mirror of
https://github.com/lucide-icons/lucide.git
synced 2026-11-10 13:18:25 +00:00
chore(lucide-react): Add Lucide React integration tests (#4804)
* Add Lucide React integration tests * Apply batched suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Remove redundant icon comparison test Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * test(lucide-react): fix browser locator markup assertions --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot Autofix powered by AI
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent
93a1d72465
commit
07a2c792ef
@@ -7,10 +7,13 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- packages/lucide-react/**
|
||||
- integrations/lucide-react/**
|
||||
- packages/shared/**
|
||||
- tools/build-icons/**
|
||||
- tools/rollup-plugins/**
|
||||
- scripts/generateNextJSAliases.mjs
|
||||
- package.json
|
||||
- pnpm-workspace.yaml
|
||||
- pnpm-lock.yaml
|
||||
|
||||
permissions:
|
||||
@@ -48,3 +51,22 @@ jobs:
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter lucide-react test
|
||||
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
cache: 'pnpm'
|
||||
node-version-file: 'package.json'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Chromium
|
||||
run: pnpm --filter @lucide/integration-react-vite exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run integration checks
|
||||
run: pnpm test:integrations:react
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
.cache
|
||||
.env
|
||||
node_modules
|
||||
.pnpm-store
|
||||
dist
|
||||
build
|
||||
sandbox
|
||||
@@ -13,6 +14,10 @@ stash
|
||||
coverage
|
||||
stats
|
||||
*.log
|
||||
*.tsbuildinfo
|
||||
integrations/**/.react-router
|
||||
integrations/**/.vitest-attachments
|
||||
integrations/**/__screenshots__
|
||||
outlined
|
||||
lucide-font
|
||||
packages/**/src/icons/*.js
|
||||
|
||||
@@ -19,6 +19,13 @@ packages/lucide-static/icon-nodes.json
|
||||
packages/lucide-static/font
|
||||
packages/svelte/.svelte-kit
|
||||
|
||||
# integration test artifacts and generated routes
|
||||
integrations/**/.react-router
|
||||
integrations/**/next-env.d.ts
|
||||
integrations/**/routeTree.gen.ts
|
||||
integrations/**/.vitest-attachments
|
||||
integrations/**/__screenshots__
|
||||
|
||||
# lucide-svelte
|
||||
packages/svelte/src/icons/*.svelte
|
||||
packages/svelte/.svelte-kit
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Lucide React integrations
|
||||
|
||||
These fixtures exercise the built `lucide-react` package in real consumer frameworks.
|
||||
|
||||
Every fixture runs three checks:
|
||||
|
||||
- a framework production build;
|
||||
- TypeScript with `noEmit`;
|
||||
- Vitest Browser Mode in Chromium.
|
||||
|
||||
From the repository root, install Chromium once and run the complete suite:
|
||||
|
||||
```sh
|
||||
pnpm --filter @lucide/integration-react-vite exec playwright install chromium
|
||||
pnpm test:integrations:react
|
||||
```
|
||||
|
||||
The fixtures depend on `lucide-react` through `workspace:*`, but import only its public package entry points. The root command builds `lucide-react` before running any consumer checks.
|
||||
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { Camera, Droplet, Edit2, House, LucideProvider, Pen } from 'lucide-react';
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
|
||||
export default function IconShowcase() {
|
||||
return (
|
||||
<main aria-label="Lucide integration">
|
||||
<Camera data-testid="static-icon" />
|
||||
<Droplet
|
||||
data-testid="custom-icon"
|
||||
className="consumer-icon"
|
||||
color="red"
|
||||
size={48}
|
||||
strokeWidth={4}
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
<LucideProvider
|
||||
color="purple"
|
||||
size={32}
|
||||
strokeWidth={3}
|
||||
>
|
||||
<House data-testid="provider-icon" />
|
||||
</LucideProvider>
|
||||
<Pen data-testid="alias-icon" />
|
||||
<Edit2 data-testid="canonical-icon" />
|
||||
<DynamicIcon
|
||||
aria-label="Dynamic circle"
|
||||
data-testid="dynamic-icon"
|
||||
name="circle"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import IconShowcase from './IconShowcase';
|
||||
|
||||
export default function Page() {
|
||||
return <IconShowcase />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/types/root-params.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@lucide/integration-react-nextjs",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "next build",
|
||||
"typecheck": "next typegen && tsc --noEmit",
|
||||
"test:browser": "vitest run",
|
||||
"check": "pnpm build && pnpm typecheck && pnpm test:browser"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "workspace:*",
|
||||
"next": "16.3.4",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "4.1.10",
|
||||
"vitest-browser-react": "2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { render } from 'vitest-browser-react';
|
||||
import Page from '../app/page';
|
||||
|
||||
test('renders Lucide public APIs in a real browser', async () => {
|
||||
const screen = await render(<Page />);
|
||||
|
||||
const staticIcon = screen.getByTestId('static-icon');
|
||||
await expect.element(staticIcon).toBeVisible();
|
||||
await expect.element(staticIcon).toHaveAttribute('width', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('height', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('viewBox', '0 0 24 24');
|
||||
await expect.element(staticIcon).toHaveAttribute('aria-hidden', 'true');
|
||||
|
||||
const customIcon = screen.getByTestId('custom-icon');
|
||||
await expect.element(customIcon).toBeVisible();
|
||||
await expect.element(customIcon).toHaveAttribute('width', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('height', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke', 'red');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke-width', '2');
|
||||
await expect.element(customIcon).toHaveClass('consumer-icon');
|
||||
|
||||
const providerIcon = screen.getByTestId('provider-icon');
|
||||
await expect.element(providerIcon).toHaveAttribute('width', '32');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke', 'purple');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke-width', '3');
|
||||
|
||||
const aliasIcon = screen.getByTestId('alias-icon');
|
||||
const canonicalIcon = screen.getByTestId('canonical-icon');
|
||||
await expect.element(aliasIcon).toBeVisible();
|
||||
await expect.element(canonicalIcon).toBeVisible();
|
||||
expect(aliasIcon.element().innerHTML).toBe(canonicalIcon.element().innerHTML);
|
||||
|
||||
const dynamicIcon = screen.getByTestId('dynamic-icon');
|
||||
await expect.element(dynamicIcon).toBeVisible();
|
||||
await expect.element(dynamicIcon).toHaveAttribute('aria-label', 'Dynamic circle');
|
||||
await expect.element(dynamicIcon).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [{ "name": "next" }],
|
||||
"types": ["vitest/globals"],
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.mts",
|
||||
],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Camera, Droplet, Edit2, House, LucideProvider, Pen } from 'lucide-react';
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
|
||||
export default function IconShowcase() {
|
||||
return (
|
||||
<main aria-label="Lucide integration">
|
||||
<Camera data-testid="static-icon" />
|
||||
<Droplet
|
||||
data-testid="custom-icon"
|
||||
className="consumer-icon"
|
||||
color="red"
|
||||
size={48}
|
||||
strokeWidth={4}
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
<LucideProvider
|
||||
color="purple"
|
||||
size={32}
|
||||
strokeWidth={3}
|
||||
>
|
||||
<House data-testid="provider-icon" />
|
||||
</LucideProvider>
|
||||
<Pen data-testid="alias-icon" />
|
||||
<Edit2 data-testid="canonical-icon" />
|
||||
<DynamicIcon
|
||||
aria-label="Dynamic circle"
|
||||
data-testid="dynamic-icon"
|
||||
name="circle"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router';
|
||||
|
||||
export function Layout({ children }: Readonly<{ children: ReactNode }>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta
|
||||
content="width=device-width, initial-scale=1"
|
||||
name="viewport"
|
||||
/>
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return <Outlet />;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { index, route, type RouteConfig } from '@react-router/dev/routes';
|
||||
|
||||
export default [
|
||||
index('./routes/home.tsx'),
|
||||
route('about', './routes/about.tsx'),
|
||||
] satisfies RouteConfig;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { Link } from 'react-router';
|
||||
|
||||
export default function About() {
|
||||
return (
|
||||
<main>
|
||||
<ArrowLeft data-testid="route-icon" />
|
||||
<Link to="/">Home</Link>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Link } from 'react-router';
|
||||
import IconShowcase from '../IconShowcase';
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<IconShowcase />
|
||||
<Link to="/about">About</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@lucide/integration-react-react-router",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "react-router build",
|
||||
"typecheck": "react-router typegen && tsc --noEmit",
|
||||
"test:browser": "vitest run",
|
||||
"check": "pnpm build && pnpm typecheck && pnpm test:browser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-router/node": "8.3.1",
|
||||
"@react-router/serve": "8.3.1",
|
||||
"isbot": "5.2.2",
|
||||
"lucide-react": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8",
|
||||
"react-router": "8.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-router/dev": "8.3.1",
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "4.1.10",
|
||||
"vitest-browser-react": "2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { Config } from '@react-router/dev/config';
|
||||
|
||||
export default {
|
||||
ssr: true,
|
||||
} satisfies Config;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { render } from 'vitest-browser-react';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router';
|
||||
import About from '../app/routes/about';
|
||||
import Home from '../app/routes/home';
|
||||
|
||||
test('renders Lucide public APIs and survives router navigation', async () => {
|
||||
const screen = await render(
|
||||
<MemoryRouter initialEntries={['/']}>
|
||||
<Routes>
|
||||
<Route
|
||||
element={<Home />}
|
||||
path="/"
|
||||
/>
|
||||
<Route
|
||||
element={<About />}
|
||||
path="/about"
|
||||
/>
|
||||
</Routes>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
const staticIcon = screen.getByTestId('static-icon');
|
||||
await expect.element(staticIcon).toBeVisible();
|
||||
await expect.element(staticIcon).toHaveAttribute('width', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('height', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('viewBox', '0 0 24 24');
|
||||
await expect.element(staticIcon).toHaveAttribute('aria-hidden', 'true');
|
||||
|
||||
const customIcon = screen.getByTestId('custom-icon');
|
||||
await expect.element(customIcon).toBeVisible();
|
||||
await expect.element(customIcon).toHaveAttribute('width', '48');
|
||||
const aliasIcon = screen.getByTestId('alias-icon');
|
||||
const canonicalIcon = screen.getByTestId('canonical-icon');
|
||||
await expect.element(aliasIcon).toBeVisible();
|
||||
await expect.element(canonicalIcon).toBeVisible();
|
||||
expect(aliasIcon.element().innerHTML).toBe(canonicalIcon.element().innerHTML);
|
||||
await expect.element(customIcon).toHaveClass('consumer-icon');
|
||||
|
||||
const providerIcon = screen.getByTestId('provider-icon');
|
||||
await expect.element(providerIcon).toHaveAttribute('width', '32');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke', 'purple');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke-width', '3');
|
||||
|
||||
const dynamicIcon = screen.getByTestId('dynamic-icon');
|
||||
await expect.element(dynamicIcon).toBeVisible();
|
||||
await expect.element(dynamicIcon).toHaveAttribute('aria-label', 'Dynamic circle');
|
||||
await expect.element(dynamicIcon).not.toHaveAttribute('aria-hidden');
|
||||
|
||||
await screen.getByRole('link', { name: 'About' }).click();
|
||||
await expect.element(screen.getByTestId('route-icon')).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"include": ["**/*", ".react-router/types/**/*"],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"types": ["node", "vite/client", "vitest/globals"],
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"jsx": "react-jsx",
|
||||
"rootDirs": [".", "./.react-router/types"],
|
||||
"verbatimModuleSyntax": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { reactRouter } from '@react-router/dev/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [reactRouter()],
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
/>
|
||||
<title>Lucide React TanStack Router integration</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script
|
||||
type="module"
|
||||
src="/src/main.tsx"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@lucide/integration-react-tanstack-router",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test:browser": "vitest run",
|
||||
"check": "pnpm build && pnpm typecheck && pnpm test:browser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-router": "1.170.32",
|
||||
"lucide-react": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tanstack/router-plugin": "1.168.35",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "4.1.10",
|
||||
"vitest-browser-react": "2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Camera, Droplet, Edit2, House, LucideProvider, Pen } from 'lucide-react';
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
|
||||
export default function IconShowcase() {
|
||||
return (
|
||||
<main aria-label="Lucide integration">
|
||||
<Camera data-testid="static-icon" />
|
||||
<Droplet
|
||||
data-testid="custom-icon"
|
||||
className="consumer-icon"
|
||||
color="red"
|
||||
size={48}
|
||||
strokeWidth={4}
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
<LucideProvider
|
||||
color="purple"
|
||||
size={32}
|
||||
strokeWidth={3}
|
||||
>
|
||||
<House data-testid="provider-icon" />
|
||||
</LucideProvider>
|
||||
<Pen data-testid="alias-icon" />
|
||||
<Edit2 data-testid="canonical-icon" />
|
||||
<DynamicIcon
|
||||
aria-label="Dynamic circle"
|
||||
data-testid="dynamic-icon"
|
||||
name="circle"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { RouterProvider } from '@tanstack/react-router';
|
||||
import { router } from './router';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<RouterProvider router={router} />
|
||||
</StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,77 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as AboutRouteImport } from './routes/about'
|
||||
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AboutRoute = AboutRouteImport.update({
|
||||
id: '/about',
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/about'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/about'
|
||||
id: '__root__' | '/' | '/about'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AboutRoute: typeof AboutRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/about': {
|
||||
id: '/about'
|
||||
path: '/about'
|
||||
fullPath: '/about'
|
||||
preLoaderRoute: typeof AboutRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AboutRoute: AboutRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createRouter } from '@tanstack/react-router';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
export const router = createRouter({ routeTree });
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Outlet, createRootRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => <Outlet />,
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Link, createFileRoute } from '@tanstack/react-router';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
|
||||
export const Route = createFileRoute('/about')({
|
||||
component: About,
|
||||
});
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<main>
|
||||
<ArrowLeft data-testid="route-icon" />
|
||||
<Link to="/">Home</Link>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Link, createFileRoute } from '@tanstack/react-router';
|
||||
import IconShowcase from '../IconShowcase';
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<>
|
||||
<IconShowcase />
|
||||
<Link to="/about">About</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createMemoryHistory, createRouter, RouterProvider } from '@tanstack/react-router';
|
||||
import { expect, test } from 'vitest';
|
||||
import { render } from 'vitest-browser-react';
|
||||
import { routeTree } from '../src/routeTree.gen';
|
||||
|
||||
test('renders Lucide public APIs and survives router navigation', async () => {
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory({ initialEntries: ['/'] }),
|
||||
routeTree,
|
||||
});
|
||||
const screen = await render(<RouterProvider router={router} />);
|
||||
|
||||
const staticIcon = screen.getByTestId('static-icon');
|
||||
await expect.element(staticIcon).toBeVisible();
|
||||
await expect.element(staticIcon).toHaveAttribute('width', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('height', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('viewBox', '0 0 24 24');
|
||||
await expect.element(staticIcon).toHaveAttribute('aria-hidden', 'true');
|
||||
|
||||
const customIcon = screen.getByTestId('custom-icon');
|
||||
await expect.element(customIcon).toBeVisible();
|
||||
await expect.element(customIcon).toHaveAttribute('width', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('height', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke', 'red');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke-width', '2');
|
||||
await expect.element(customIcon).toHaveClass('consumer-icon');
|
||||
|
||||
const providerIcon = screen.getByTestId('provider-icon');
|
||||
await expect.element(providerIcon).toHaveAttribute('width', '32');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke', 'purple');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke-width', '3');
|
||||
|
||||
const aliasIcon = screen.getByTestId('alias-icon');
|
||||
const canonicalIcon = screen.getByTestId('canonical-icon');
|
||||
await expect.element(aliasIcon).toBeVisible();
|
||||
await expect.element(canonicalIcon).toBeVisible();
|
||||
expect(aliasIcon.element().innerHTML).toBe(canonicalIcon.element().innerHTML);
|
||||
|
||||
const dynamicIcon = screen.getByTestId('dynamic-icon');
|
||||
await expect.element(dynamicIcon).toBeVisible();
|
||||
await expect.element(dynamicIcon).toHaveAttribute('aria-label', 'Dynamic circle');
|
||||
await expect.element(dynamicIcon).not.toHaveAttribute('aria-hidden');
|
||||
|
||||
await screen.getByRole('link', { name: 'About' }).click();
|
||||
await expect.element(screen.getByTestId('route-icon')).toBeVisible();
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["vite/client", "vitest/globals"],
|
||||
},
|
||||
"include": ["src", "tests", "vite.config.ts", "vitest.config.ts"],
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
tanstackRouter({
|
||||
target: 'react',
|
||||
autoCodeSplitting: true,
|
||||
}),
|
||||
react(),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig, mergeConfig } from 'vitest/config';
|
||||
import viteConfig from './vite.config';
|
||||
|
||||
export default mergeConfig(
|
||||
viteConfig,
|
||||
defineConfig({
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@lucide/integration-react-tanstack-start",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test:browser": "vitest run",
|
||||
"check": "pnpm build && pnpm typecheck && pnpm test:browser"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-router": "1.170.32",
|
||||
"@tanstack/react-start": "1.168.49",
|
||||
"lucide-react": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^26.1.1",
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "4.1.10",
|
||||
"vitest-browser-react": "2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Camera, Droplet, Edit2, House, LucideProvider, Pen } from 'lucide-react';
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
|
||||
export default function IconShowcase() {
|
||||
return (
|
||||
<main aria-label="Lucide integration">
|
||||
<Camera data-testid="static-icon" />
|
||||
<Droplet
|
||||
data-testid="custom-icon"
|
||||
className="consumer-icon"
|
||||
color="red"
|
||||
size={48}
|
||||
strokeWidth={4}
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
<LucideProvider
|
||||
color="purple"
|
||||
size={32}
|
||||
strokeWidth={3}
|
||||
>
|
||||
<House data-testid="provider-icon" />
|
||||
</LucideProvider>
|
||||
<Pen data-testid="alias-icon" />
|
||||
<Edit2 data-testid="canonical-icon" />
|
||||
<DynamicIcon
|
||||
aria-label="Dynamic circle"
|
||||
data-testid="dynamic-icon"
|
||||
name="circle"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/* eslint-disable */
|
||||
|
||||
// @ts-nocheck
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
// This file was automatically generated by TanStack Router.
|
||||
// You should NOT make any changes in this file as it will be overwritten.
|
||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||
|
||||
import { Route as rootRouteImport } from './routes/__root'
|
||||
import { Route as IndexRouteImport } from './routes/index'
|
||||
import { Route as AboutRouteImport } from './routes/about'
|
||||
|
||||
const IndexRoute = IndexRouteImport.update({
|
||||
id: '/',
|
||||
path: '/',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
const AboutRoute = AboutRouteImport.update({
|
||||
id: '/about',
|
||||
path: '/about',
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
}
|
||||
export interface FileRoutesByTo {
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
}
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRouteImport
|
||||
'/': typeof IndexRoute
|
||||
'/about': typeof AboutRoute
|
||||
}
|
||||
export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths: '/' | '/about'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to: '/' | '/about'
|
||||
id: '__root__' | '/' | '/about'
|
||||
fileRoutesById: FileRoutesById
|
||||
}
|
||||
export interface RootRouteChildren {
|
||||
IndexRoute: typeof IndexRoute
|
||||
AboutRoute: typeof AboutRoute
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface FileRoutesByPath {
|
||||
'/': {
|
||||
id: '/'
|
||||
path: '/'
|
||||
fullPath: '/'
|
||||
preLoaderRoute: typeof IndexRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
'/about': {
|
||||
id: '/about'
|
||||
path: '/about'
|
||||
fullPath: '/about'
|
||||
preLoaderRoute: typeof AboutRouteImport
|
||||
parentRoute: typeof rootRouteImport
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
IndexRoute: IndexRoute,
|
||||
AboutRoute: AboutRoute,
|
||||
}
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createRouter } from '@tanstack/react-router';
|
||||
import { routeTree } from './routeTree.gen';
|
||||
|
||||
export function getRouter() {
|
||||
return createRouter({
|
||||
routeTree,
|
||||
scrollRestoration: true,
|
||||
});
|
||||
}
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: ReturnType<typeof getRouter>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { HeadContent, Outlet, Scripts, createRootRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
head: () => ({
|
||||
meta: [
|
||||
{ charSet: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ title: 'Lucide React TanStack Start integration' },
|
||||
],
|
||||
}),
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<RootDocument>
|
||||
<Outlet />
|
||||
</RootDocument>
|
||||
);
|
||||
}
|
||||
|
||||
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<HeadContent />
|
||||
</head>
|
||||
<body>
|
||||
{children}
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Link, createFileRoute } from '@tanstack/react-router';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
|
||||
export const Route = createFileRoute('/about')({
|
||||
component: About,
|
||||
});
|
||||
|
||||
function About() {
|
||||
return (
|
||||
<main>
|
||||
<ArrowLeft data-testid="route-icon" />
|
||||
<Link to="/">Home</Link>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Link, createFileRoute } from '@tanstack/react-router';
|
||||
import IconShowcase from '../IconShowcase';
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: Home,
|
||||
});
|
||||
|
||||
function Home() {
|
||||
return (
|
||||
<>
|
||||
<IconShowcase />
|
||||
<Link to="/about">About</Link>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { render } from 'vitest-browser-react';
|
||||
import IconShowcase from '../src/IconShowcase';
|
||||
|
||||
test('renders Lucide public APIs in a real browser', async () => {
|
||||
const screen = await render(<IconShowcase />);
|
||||
|
||||
const staticIcon = screen.getByTestId('static-icon');
|
||||
await expect.element(staticIcon).toBeVisible();
|
||||
await expect.element(staticIcon).toHaveAttribute('width', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('height', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('viewBox', '0 0 24 24');
|
||||
await expect.element(staticIcon).toHaveAttribute('aria-hidden', 'true');
|
||||
|
||||
const customIcon = screen.getByTestId('custom-icon');
|
||||
await expect.element(customIcon).toBeVisible();
|
||||
await expect.element(customIcon).toHaveAttribute('width', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('height', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke', 'red');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke-width', '2');
|
||||
await expect.element(customIcon).toHaveClass('consumer-icon');
|
||||
|
||||
const providerIcon = screen.getByTestId('provider-icon');
|
||||
await expect.element(providerIcon).toHaveAttribute('width', '32');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke', 'purple');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke-width', '3');
|
||||
|
||||
const aliasIcon = screen.getByTestId('alias-icon');
|
||||
const canonicalIcon = screen.getByTestId('canonical-icon');
|
||||
await expect.element(aliasIcon).toBeVisible();
|
||||
await expect.element(canonicalIcon).toBeVisible();
|
||||
expect(aliasIcon.element().innerHTML).toBe(canonicalIcon.element().innerHTML);
|
||||
|
||||
const dynamicIcon = screen.getByTestId('dynamic-icon');
|
||||
await expect.element(dynamicIcon).toBeVisible();
|
||||
await expect.element(dynamicIcon).toHaveAttribute('aria-label', 'Dynamic circle');
|
||||
await expect.element(dynamicIcon).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["node", "vite/client", "vitest/globals"],
|
||||
},
|
||||
"include": ["src", "tests", "vite.config.ts", "vitest.config.ts"],
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tanstackStart(), react()],
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0"
|
||||
/>
|
||||
<title>Lucide React Vite integration</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script
|
||||
type="module"
|
||||
src="/src/main.tsx"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@lucide/integration-react-vite",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"test:browser": "vitest run",
|
||||
"check": "pnpm build && pnpm typecheck && pnpm test:browser"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "workspace:*",
|
||||
"react": "19.2.8",
|
||||
"react-dom": "19.2.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "19.2.18",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@vitejs/plugin-react": "^4.7.0",
|
||||
"@vitest/browser-playwright": "4.1.10",
|
||||
"playwright": "1.58.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^7.3.6",
|
||||
"vitest": "4.1.10",
|
||||
"vitest-browser-react": "2.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import IconShowcase from './IconShowcase';
|
||||
|
||||
export default function App() {
|
||||
return <IconShowcase />;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Camera, Droplet, Edit2, House, LucideProvider, Pen } from 'lucide-react';
|
||||
import { DynamicIcon } from 'lucide-react/dynamic';
|
||||
|
||||
export default function IconShowcase() {
|
||||
return (
|
||||
<main aria-label="Lucide integration">
|
||||
<Camera data-testid="static-icon" />
|
||||
<Droplet
|
||||
data-testid="custom-icon"
|
||||
className="consumer-icon"
|
||||
color="red"
|
||||
size={48}
|
||||
strokeWidth={4}
|
||||
absoluteStrokeWidth
|
||||
/>
|
||||
<LucideProvider
|
||||
color="purple"
|
||||
size={32}
|
||||
strokeWidth={3}
|
||||
>
|
||||
<House data-testid="provider-icon" />
|
||||
</LucideProvider>
|
||||
<Pen data-testid="alias-icon" />
|
||||
<Edit2 data-testid="canonical-icon" />
|
||||
<DynamicIcon
|
||||
aria-label="Dynamic circle"
|
||||
data-testid="dynamic-icon"
|
||||
name="circle"
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { render } from 'vitest-browser-react';
|
||||
import App from '../src/App';
|
||||
|
||||
test('renders Lucide public APIs in a real browser', async () => {
|
||||
const screen = await render(<App />);
|
||||
|
||||
const staticIcon = screen.getByTestId('static-icon');
|
||||
await expect.element(staticIcon).toBeVisible();
|
||||
await expect.element(staticIcon).toHaveAttribute('width', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('height', '24');
|
||||
await expect.element(staticIcon).toHaveAttribute('viewBox', '0 0 24 24');
|
||||
await expect.element(staticIcon).toHaveAttribute('aria-hidden', 'true');
|
||||
|
||||
const customIcon = screen.getByTestId('custom-icon');
|
||||
await expect.element(customIcon).toBeVisible();
|
||||
await expect.element(customIcon).toHaveAttribute('width', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('height', '48');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke', 'red');
|
||||
await expect.element(customIcon).toHaveAttribute('stroke-width', '2');
|
||||
await expect.element(customIcon).toHaveClass('consumer-icon');
|
||||
|
||||
const providerIcon = screen.getByTestId('provider-icon');
|
||||
await expect.element(providerIcon).toHaveAttribute('width', '32');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke', 'purple');
|
||||
await expect.element(providerIcon).toHaveAttribute('stroke-width', '3');
|
||||
|
||||
const aliasIcon = screen.getByTestId('alias-icon');
|
||||
const canonicalIcon = screen.getByTestId('canonical-icon');
|
||||
await expect.element(aliasIcon).toBeVisible();
|
||||
await expect.element(canonicalIcon).toBeVisible();
|
||||
expect(aliasIcon.element().innerHTML).toBe(canonicalIcon.element().innerHTML);
|
||||
|
||||
const dynamicIcon = screen.getByTestId('dynamic-icon');
|
||||
await expect.element(dynamicIcon).toBeVisible();
|
||||
await expect.element(dynamicIcon).toHaveAttribute('aria-label', 'Dynamic circle');
|
||||
await expect.element(dynamicIcon).not.toHaveAttribute('aria-hidden');
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["vitest/globals"],
|
||||
},
|
||||
"include": ["src", "tests", "vite.config.ts"],
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
browser: {
|
||||
enabled: true,
|
||||
headless: true,
|
||||
provider: playwright(),
|
||||
instances: [{ browser: 'chromium' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -5,6 +5,7 @@
|
||||
"build": "pnpm -r --filter './packages/**' build",
|
||||
"test": "pnpm -r --filter './packages/**' test",
|
||||
"test:update": "pnpm -r --filter './packages/**' --filter !'./packages/lucide-angular' test -- -u",
|
||||
"test:integrations:react": "pnpm --filter lucide-react build && pnpm -r --workspace-concurrency=1 --filter './integrations/lucide-react/**' check",
|
||||
"test:font": "pnpm --filter build-font test",
|
||||
"lucide": "pnpm --filter lucide",
|
||||
"lucide-angular": "pnpm --filter lucide-angular",
|
||||
|
||||
Generated
+1765
-183
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@ packages:
|
||||
- 'packages/*'
|
||||
- 'tools/*'
|
||||
- 'docs'
|
||||
- 'integrations/*/*'
|
||||
|
||||
overrides:
|
||||
cross-spawn: 7.0.5
|
||||
|
||||
Reference in New Issue
Block a user