Skip to content

Commit

Permalink
test: add more coverage (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
metonym authored Feb 8, 2025
1 parent b20acef commit 1bda879
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 27 deletions.
138 changes: 138 additions & 0 deletions tests/OptimizeCssPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { CarbonSvelte } from "../src/constants";
import OptimizeCssPlugin from "../src/plugins/OptimizeCssPlugin";

// Mock webpack compiler and related types
const createMockCompiler = (options: any = {}) => {
const { assets = {}, fileDependencies = [] } = options;

const compilation = {
hooks: {
processAssets: {
tap: jest.fn((_, callback) => {
callback(assets);
}),
},
},
updateAsset: jest.fn(),
};

const normalModuleHooks = {
beforeSnapshot: {
tap: jest.fn((_, callback) => {
callback({ buildInfo: { fileDependencies } });
}),
},
};

return {
hooks: {
thisCompilation: {
tap: jest.fn((_, callback) => callback(compilation)),
},
},
webpack: {
Compilation: {
PROCESS_ASSETS_STAGE_DERIVED: "PROCESS_ASSETS_STAGE_DERIVED",
},
NormalModule: {
getCompilationHooks: () => normalModuleHooks,
},
sources: {
RawSource: jest.fn((content) => ({ source: () => content })),
},
},
compilation,
normalModuleHooks,
};
};

describe("OptimizeCssPlugin", () => {
test("constructor sets default options correctly", () => {
const plugin = new OptimizeCssPlugin();
expect((plugin as any).options).toEqual({
verbose: true,
preserveAllIBMFonts: false,
});
});

test("constructor respects provided options", () => {
const plugin = new OptimizeCssPlugin({
verbose: false,
preserveAllIBMFonts: true,
});
expect((plugin as any).options).toEqual({
verbose: false,
preserveAllIBMFonts: true,
});
});

test("skips processing if no Carbon Svelte imports are found", () => {
const plugin = new OptimizeCssPlugin();
const mockCompiler = createMockCompiler({
assets: { "styles.css": { source: () => "body { color: red; }" } },
fileDependencies: ["regular-component.svelte"],
});

plugin.apply(mockCompiler as any);

expect(mockCompiler.compilation.updateAsset).not.toHaveBeenCalled();
});

test("processes CSS files when Carbon Svelte imports are found", () => {
const plugin = new OptimizeCssPlugin();
const carbonComponent = `node_modules/${CarbonSvelte.Components}/Button.svelte`;
const cssContent = ".bx--btn { color: blue; }";

const mockCompiler = createMockCompiler({
assets: {
"styles.css": { source: () => cssContent },
},
fileDependencies: [carbonComponent],
});

plugin.apply(mockCompiler as any);

expect(mockCompiler.compilation.updateAsset).toHaveBeenCalledWith(
"styles.css",
expect.any(Object),
);
});

test("handles Buffer input correctly", () => {
const plugin = new OptimizeCssPlugin();
const carbonComponent = `node_modules/${CarbonSvelte.Components}/Button.svelte`;
const cssContent = Buffer.from(".bx--btn { color: blue; }");

const mockCompiler = createMockCompiler({
assets: {
"styles.css": { source: () => cssContent },
},
fileDependencies: [carbonComponent],
});

plugin.apply(mockCompiler as any);

expect(mockCompiler.compilation.updateAsset).toHaveBeenCalledWith(
"styles.css",
expect.any(Object),
);
});

test("respects verbose option for printing diff", () => {
const consoleSpy = jest.spyOn(console, "log").mockImplementation();
const plugin = new OptimizeCssPlugin({ verbose: true });
const carbonComponent = `node_modules/${CarbonSvelte.Components}/Button.svelte`;

const mockCompiler = createMockCompiler({
assets: {
"styles.css": { source: () => ".bx--btn { color: blue; }" },
},
fileDependencies: [carbonComponent],
});

plugin.apply(mockCompiler as any);

expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
73 changes: 46 additions & 27 deletions tests/extract-selectors.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { describe, expect, test } from "bun:test";
import { extractSelectors } from "../scripts/extract-selectors";

describe("extractSelectors", () => {
Expand All @@ -7,6 +8,7 @@ describe("extractSelectors", () => {
filename: "test.svelte",
});
expect(result.classes).toEqual([".test-class"]);
expect(result.components).toEqual([]);
});

test("extracts multiple classes from class attribute", () => {
Expand All @@ -15,70 +17,87 @@ describe("extractSelectors", () => {
filename: "test.svelte",
});
expect(result.classes).toEqual([".class1", ".class2", ".class3"]);
expect(result.components).toEqual([]);
});

test("extracts Carbon classes with bx-- prefix", () => {
const result = extractSelectors({
code: '<div class="bx--btn bx--modal"></div>',
filename: "test.svelte",
});
expect(result.classes).toEqual([".bx--btn", ".bx--modal"]);
});

test("extracts class directives", () => {
const result = extractSelectors({
code: "<div class:active={isActive}></div>",
code: "<div class:active={isActive} class:bx--selected={isSelected}></div>",
filename: "test.svelte",
});
expect(result.classes).toEqual([".active"]);
expect(result.classes).toEqual([".active", ".bx--selected"]);
});

test("extracts global selectors", () => {
test("extracts classes from dynamic expressions", () => {
const result = extractSelectors({
code: "<style>:global(div) { color: red; }</style>",
code: "<div class=\"{dynamic} static-class {condition ? 'bx--active' : ''}\"></div>",
filename: "test.svelte",
});
expect(result.classes).toEqual([".div"]);
expect(result.classes).toEqual([".static-class", ".bx--active"]);
});

test("handles Carbon prefix classes", () => {
test("extracts global selectors", () => {
const result = extractSelectors({
code: '<div class="bx--button bx--text"></div>',
code: "<style>:global(.bx--global-class) { color: red; }</style>",
filename: "test.svelte",
});
expect(result.classes).toEqual([".bx--button", ".bx--text"]);
expect(result.classes).toEqual([".bx--global-class"]);
});

test("deduplicates repeated classes", () => {
test("extracts component references", () => {
const result = extractSelectors({
code: '<div class="test test test"></div>',
code: `
<script>
import { Button, Modal } from 'carbon-components-svelte';
</script>
<Button />
<Modal />
<svelte:component this={DynamicComponent} />
`,
filename: "test.svelte",
});
expect(result.classes).toEqual([".test"]);
expect(result.components).toEqual(["Button", "Modal", "DynamicComponent"]);
});

test("handles mixed scenarios", () => {
test("handles template literals with Carbon classes", () => {
const result = extractSelectors({
code: `
<div class="regular-class bx--carbon-class">
<span class:active={isActive}></span>
<style>:global(body) { margin: 0; }</style>
</div>
<script>
const className = \`bx--template-class\`;
</script>
<div class={className}></div>
`,
filename: "test.svelte",
});
expect(result.classes).toEqual([
".regular-class",
".bx--carbon-class",
".active",
]);
expect(result.classes).toEqual([".bx--template-class"]);
});

test("handles empty class attributes", () => {
test("deduplicates classes and components", () => {
const result = extractSelectors({
code: '<div class=""></div>',
code: `
<div class="duplicate duplicate bx--duplicate bx--duplicate"></div>
<Button />
<Button />
`,
filename: "test.svelte",
});
expect(result.classes).toEqual([]);
expect(result.classes).toEqual([".duplicate", ".bx--duplicate"]);
expect(result.components).toEqual(["Button"]);
});

test("handles inline components", () => {
test("handles empty and whitespace-only classes", () => {
const result = extractSelectors({
code: "<div><Test /><Component /></div>",
code: '<div class=" "></div>',
filename: "test.svelte",
});
expect(result.components).toEqual(["Test", "Component"]);
expect(result.classes).toEqual([]);
});
});
56 changes: 56 additions & 0 deletions tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { CarbonSvelte } from "../src/constants";
import { isCarbonSvelteImport, isCssFile, isSvelteFile } from "../src/utils";

describe("isSvelteFile", () => {
test("returns true for .svelte files", () => {
expect(isSvelteFile("Component.svelte")).toBe(true);
expect(isSvelteFile("path/to/Component.svelte")).toBe(true);
expect(isSvelteFile("./Component.svelte")).toBe(true);
});

test("returns false for non-svelte files", () => {
expect(isSvelteFile("Component.js")).toBe(false);
expect(isSvelteFile("Component.css")).toBe(false);
expect(isSvelteFile("Component")).toBe(false);
expect(isSvelteFile("Component.svelte.js")).toBe(false);
});
});

describe("isCssFile", () => {
test("returns true for .css files", () => {
expect(isCssFile("styles.css")).toBe(true);
expect(isCssFile("path/to/styles.css")).toBe(true);
expect(isCssFile("./styles.css")).toBe(true);
});

test("returns false for non-css files", () => {
expect(isCssFile("styles.scss")).toBe(false);
expect(isCssFile("styles.less")).toBe(false);
expect(isCssFile("styles")).toBe(false);
expect(isCssFile("styles.css.js")).toBe(false);
});
});

describe("isCarbonSvelteImport", () => {
test("returns true for Carbon Svelte component imports", () => {
expect(
isCarbonSvelteImport(
`node_modules/${CarbonSvelte.Components}/Button.svelte`,
),
).toBe(true);
expect(
isCarbonSvelteImport(`${CarbonSvelte.Components}/Button.svelte`),
).toBe(true);
});

test("returns false for non-Carbon Svelte imports", () => {
expect(isCarbonSvelteImport("Button.svelte")).toBe(false);
expect(isCarbonSvelteImport(`${CarbonSvelte.Icons}/Button.svelte`)).toBe(
false,
);
expect(isCarbonSvelteImport(`${CarbonSvelte.Components}/Button.js`)).toBe(
false,
);
expect(isCarbonSvelteImport("other-lib/Button.svelte")).toBe(false);
});
});

0 comments on commit 1bda879

Please sign in to comment.