Internals
Testing Internals
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.
Unit Test Coverage
Line, function, and branch coverage from the Vitest unit test suite. CLI entry points (index.ts, transpile.ts, page-worker.ts) are excluded. They run only in E2E tests below.
| Metric | Coverage | Covered / Total |
|---|---|---|
| Lines | 95.1% | 2522 / 2653 |
| Statements | 94.0% | 2693 / 2866 |
| Functions | 93.1% | 419 / 450 |
| Branches | 85.1% | 1302 / 1530 |
Regenerate with yarn docs:update-coverage
E2E Coverage (Playwright + V8)
Coverage captured while the CLI builds the 55-page E2E fixture site. Exercises the full transpilation pipeline end-to-end including the CLI entry point, startup, and worker threads that unit tests cannot reach.
| Metric | Coverage | Covered / Total |
|---|---|---|
| Lines | 76.3% | 5644 / 7396 |
| Statements | 76.3% | 5644 / 7396 |
| Functions | 76.8% | 152 / 198 |
| Branches | 73.1% | 631 / 863 |
Regenerate with yarn docs:update-e2e-coverage
Scaffolding CLI Coverage (create-bascik)
Coverage from the create-bascik Vitest test suite, verifying the project initializer and template generator scaffolding logic.
| Metric | Coverage | Covered / Total |
|---|---|---|
| Lines | 100.0% | 43 / 43 |
| Statements | 100.0% | 44 / 44 |
| Functions | 100.0% | 8 / 8 |
| Branches | 100.0% | 5 / 5 |
Regenerate with yarn create:update-coverage
VS Code Extension Coverage (bascik-vscode)
Coverage from the bascik-vscode extension unit test suite, verifying compatibility rules and diagnostic engine logic.
| Metric | Coverage | Covered / Total |
|---|---|---|
| Lines | 100.0% | 6 / 6 |
| Statements | 100.0% | 7 / 7 |
| Functions | 100.0% | 3 / 3 |
| Branches | 100.0% | 2 / 2 |
Regenerate with yarn ext:update-coverage
Documentation Build Scripts Coverage (bascik-docs)
Coverage from the bascik-docs build-time schema generators, Open Graph builders, and markdown processing utilities.
| Metric | Coverage | Covered / Total |
|---|---|---|
| Lines | 89.3% | 492 / 551 |
| Statements | 85.3% | 550 / 645 |
| Functions | 94.0% | 63 / 67 |
| Branches | 72.9% | 336 / 461 |
Regenerate with yarn docs:update-scripts-coverage
Running Unit Tests
Commands can be run per-package or across the workspace from the repository root:
# Workspace-wide unit tests
yarn unit:all
# Package-specific unit tests (single run)
yarn pkg:unit # @bascik/bascik
yarn create:unit # create-bascik
yarn ext:unit # bascik-vscode
# Interactive watch mode (pkg)
yarn pkg:test
# Single run with coverage
yarn pkg:coverage
yarn create:coverage
yarn ext:coverage
yarn coverage:all # update coverage across all packages
# Benchmarks
yarn pkg:benchRunning E2E Tests
End-to-end tests are run via:
# Static production server suite
yarn pkg:e2e
# Dev server suite (runs full E2E test suite + live-reload tests against bascik --dev)
yarn pkg:e2e:dev
# Production server suite (runs both HTTP/1.1 cleartext and HTTP/2 TLS server script tests against bascik --serve)
yarn pkg:e2e:prod
# Or run HTTP/1.1 and HTTP/2 prod server suites individually:
yarn pkg:e2e:prod:http1
yarn pkg:e2e:prod:http2This 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 pkg:build && yarn pkg:e2eTo run a specific test file or use the Playwright UI:
# Run only CSS scoping tests against static server
npx playwright test --config e2e/playwright.config.ts e2e/tests/css-scoping.test.ts
# Run dev server live-reload tests against bascik --dev
npx playwright test --config e2e/playwright.dev.config.ts e2e/tests/dev-server-reload.test.ts
# Run HTTP/1.1 prod server tests against bascik --serve
npx playwright test --config e2e/playwright.server.config.ts e2e/tests/prod-server.test.ts
# Run HTTP/2 prod server tests against bascik --serve
npx playwright test --config e2e/playwright.server-http2.config.ts e2e/tests/prod-server.test.ts
# Open the Playwright UI for interactive debugging
npx 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.ts ← fixture config (minify.identifiers: false)
playwright.config.ts ← static build test runner
playwright.server.config.ts ← HTTP/1.1 cleartext prod server runner
playwright.server-http2.config.ts← HTTP/2 TLS prod server runner
playwright.dev.config.ts ← dev server runner for live-reload and open-page priority
server.ts ← minimal static HTTP server for dist/
src/
pages/ ← one HTML page per feature under test
components/ ← components used by those pages
tests/ ← Playwright test filesThe E2E suite supports four execution modes:
- Static production suite (
playwright.config.ts): builds the fixture site withbascik --buildand serves static files viaserver.tson port 4200. - HTTP/1.1 production server suite (
playwright.server.config.ts): boots cleartextbascik --serveover HTTP/1.1 on port 9443 to testdata-bascik-serverrequest-time script execution and cleartext server behavior. - HTTP/2 production server suite (
playwright.server-http2.config.ts): boots TLS-enabledbascik --serveover HTTP/2 on port 9444 to testdata-bascik-serverrequest-time script execution and encrypted server behavior. - Dev server watch suite (
playwright.dev.config.ts): bootsbascik --devon port 8080 to run the full test suite and live-reload watcher tests directly against the live dev server with SSE tracking and open-page priority re-transpilation.
Tests navigate to pages on the active server and assert against the live browser DOM.
Fixture Design
minify.identifiers is kept at false in the fixture config so Playwright selectors can use readable scoped names like bascik__my-comp__btn. The only non-default values set are the site URL and the production server port:
// pkg/e2e/bascik.config.ts
import { defineConfig } from '@bascik/bascik/config';
export default defineConfig({
siteUrl: 'http://localhost:4200',
useWorkers: true,
prodServer: { port: 9443 },
});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.
Compiler Verification
When designing and updating end-to-end tests, these tests explicitly verify that Bascik's scoping and compilation rules transpile and rewrite selectors correctly. As a result, they deliberately select and assert against exact compiled class names (e.g., .bascik__my-comp__wrapper) and rewritten component IDs (e.g., [id$="__btn"]). They must not use data-testid properties because doing so would bypass the verification of the scoping engine itself.
E2E 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.
Unit 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 },
isBuild: false,
minify: { css: false, identifiers: 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 initializes 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
Run type checking across all packages or for individual packages from the repository root:
# Type check all packages
yarn typecheck:all
# Package-specific type checks
yarn pkg:typecheck
yarn create:typecheck
yarn ext:typecheckMonorepo Aggregator Commands
The root package.json provides aggregated tasks across all projects:
yarn typecheck:all: runs typechecks across all packages in the workspaceyarn check:all: runs spelling (check:spelling) and web standards (check:standards)yarn unit:all: runs unit test suites across all packagesyarn e2e:all: runs Playwright E2E suites across the workspaceyarn coverage:all: generates and updates coverage reports across all packagesyarn test:all: runs typechecks, spelling/standards checks, unit tests, and E2E suites in sequence (coverage excluded)
Contributing 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 pkg:unitand ensure all tests pass. - Run
yarn pkg:typecheckto confirm there are no TypeScript errors. - Open a pull request against
main.