Internals
Testing
Bascik has two separate test suites: unit tests (Vitest) that verify individual library modules, and end-to-end tests (Playwright) that build and browser-test the full transpilation pipeline against a fixture site.
Running Unit Tests
All commands are run from the pkg/ directory:
# Interactive watch mode (re-runs on file changes)
yarn test
# Single run (used in CI)
yarn test:ci
# Single run with full coverage report
yarn test:coverage
# Run benchmarks
yarn benchRunning E2E Tests
End-to-end tests are also run from pkg/:
yarn e2eThis builds the fixture site (using the current dist/) and then runs Playwright against it. The first run requires the package to be built first:
yarn build && yarn e2eTo run a specific test file or use the Playwright UI:
# Run only CSS scoping tests
node_modules/.bin/playwright test --config e2e/playwright.config.ts e2e/tests/css-scoping.test.ts
# Open the Playwright UI for interactive debugging
node_modules/.bin/playwright test --config e2e/playwright.config.ts --uiHow the E2E Suite Works
The e2e fixture is a small but complete Bascik project at pkg/e2e/:
pkg/e2e/
bascik.config.js ← fixture config (obfuscateAttributeNames: false)
playwright.config.ts ← Playwright config; builds + serves fixture before tests
server.mjs ← minimal static HTTP server for dist/
src/
pages/ ← one HTML page per feature under test
components/ ← components used by those pages
tests/ ← Playwright test files (one per page)Playwright's webServer hook runs two commands before any test:
node dist/index.js --config bascik.config.js --build: transpiles the fixture site intoe2e/dist/node server.mjs 4200: servesdist/onhttp://localhost:4200
Tests then navigate to pages on that server and assert against the live browser DOM.
Fixture Design
obfuscateAttributeNames is set to false in the fixture config so Playwright selectors can use readable scoped names:
// pkg/e2e/bascik.config.js
export const bascikConfig = {
obfuscateAttributeNames: false, // keeps names like bascik__my-comp__btn
scopeScriptBlocks: true,
scopeAttribute: { class: true, id: true, name: true },
};Each fixture page renders two or more instances of the component under test so isolation can be verified, changes to instance A must not affect instance B.
Test Files
Each test file is paired with a fixture page. See the full list on GitHub.
Writing a New E2E Test
- Add a fixture component in
pkg/e2e/src/components/my-feature/with an HTML file (and CSS/JS as needed). - Add a fixture page in
pkg/e2e/src/pages/my-feature-test.htmlthat renders two or more instances of the component. - Add a test file at
pkg/e2e/tests/my-feature.test.ts.
A typical test file:
import { test, expect, type Locator } from '@playwright/test';
function getInstance(page, n: number): Locator {
return page.locator('.bascik__my-feature__wrapper').nth(n);
}
test.describe('my-feature-test page', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/my-feature-test');
});
test('instances are isolated', async ({ page }) => {
const a = getInstance(page, 0);
const b = getInstance(page, 1);
// assert that state in A does not affect B
});
});Rebuild before testing. Playwright tests run against e2e/dist/, which is built from the current pkg/dist/. If you change pkg/src/, run yarn build before yarn e2e so the fixture picks up the latest transpiler.
Test Configuration
Vitest is configured in pkg/vite.config.js:
export default defineConfig({
test: {
include: ["src/**/*.test.ts"],
benchmark: {
include: ["bench/**/*.bench.ts"],
},
coverage: {
provider: "v8",
reporter: ["text", "json-summary", "lcov"],
reportsDirectory: "./coverage",
include: ["src/**/*.js"],
exclude: ["src/**/*.test.ts"],
},
},
});Coverage is collected via V8 and written to pkg/coverage/. The CI script uses text-summary only. The full HTML report at coverage/index.html is useful locally.
Test Files
Each library module has a paired test file. See the full list on GitHub.
Writing Tests
Tests use the standard Vitest describe / it / expect API. Because library modules depend on BascikConfig (a module-level singleton), tests that need a specific configuration use vi.mock to stub it:
import { describe, expect, it, vi } from "vitest";
vi.mock("../config.ts", () => ({
BascikConfig: {
scopeScriptBlocks: true,
scopeAttribute: { class: true, id: true, name: true },
obfuscateAttributeNames: false,
isBuild: false,
minifyStyles: false,
},
}));
// Import the module under test AFTER mocking its dependencies
import { prefixElementAttribute } from "./javascript.js";
describe("prefixElementAttribute", () => {
it("scopes class attributes in HTML", () => {
const component = {
name: "my-comp",
fileContent: '<div class="btn">Click</div>',
};
const result = prefixElementAttribute(component, "class", "abc123");
expect(result.fileContent).toContain("bascik__my-comp__btn");
});
});Important: Always import the module under test after calling vi.mock. Vitest hoists mock calls to the top of the file, but the import order still matters for ensuring the mock is in place when the module initialises its dependencies.
Benchmarks
Performance benchmarks live in pkg/bench/ and use Vitest's built-in bench API. They measure the transpilation pipeline on fixed, repeatable inputs:
import { bench, describe } from "vitest";
import { recursivelyTranspile } from "../src/lib/processing.ts";
describe("recursivelyTranspile", () => {
bench("simple page - one component", () => {
recursivelyTranspile(simpleHtml, componentList);
});
bench("complex page - nested components", () => {
recursivelyTranspile(complexHtml, componentList);
});
});TypeScript Checking
The package uses two tsconfig files:
tsconfig.json: used by Vitest; includes test files (src/**/*.test.ts,bench/**/*.bench.ts).tsconfig.build.json: used bytscfor production builds; excludes test files and emits todist/.
Run type checking without emitting output:
yarn typecheckContributing a Fix
- Fork the repository and create a branch.
- Make your changes in
pkg/src/. - Add or update tests in the paired
*.test.tsfile. - Run
yarn testand ensure all tests pass. - Run
yarn typecheckto confirm there are no TypeScript errors. - Open a pull request against
main.