Skip to content

feat: validate fixtures #39

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ npm-debug.log
dist
_site
packages/*/coverage
/test-results
/playwright-report
199 changes: 199 additions & 0 deletions docs/FIXTURE_VALIDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# CSS if() Polyfill Fixture Validation

This document explains the fixture validation system for the CSS if() polyfill, which ensures that your CSS transformations work correctly in real browser environments.

## Overview

The fixture validation system uses **Playwright** to test CSS fixture pairs in a headless Chromium browser. This approach validates that:

1. **Input CSS** (with `if()` functions) gets properly transformed by the polyfill
2. **Expected CSS** (the desired output) produces the same visual results
3. **Media queries** respond correctly to viewport changes
4. **@supports queries** work as expected

## How It Works

### 1. Fixture Structure

Fixtures come in pairs located in `test/fixtures/`:

- `*.input.css` - CSS containing `if()` functions
- `*.expected.css` - Expected transformation result

Example:

```text
test/fixtures/
├── basic-media.input.css # if(media(max-width: 768px): 100%; else: 50%)
├── basic-media.expected.css # Standard media query equivalent
├── basic-style.input.css # if(style(--theme): var(--primary); else: blue)
└── basic-style.expected.css # Resolved conditional styles
```

### 2. Browser Testing Process

For each fixture pair, the system:

1. **Creates an HTML page** with both input and expected CSS
2. **Loads the polyfill** and applies it to the input CSS
3. **Captures computed styles** from the polyfilled version
4. **Switches to expected CSS** and captures those styles
5. **Compares the results** to ensure they match

### 3. Validation Types

#### Basic Style Validation

Compares computed styles for properties like:

- `color`
- `width`
- `display`
- `backgroundColor`
- `fontSize`
- `margin`, `padding`, `border`

#### Media Query Testing

Tests responsive behavior at different viewports:

- Desktop (1200x800)
- Tablet (768x600)
- Mobile (375x667)

#### @supports Testing

Validates CSS feature detection with properties like:

- `display: grid`
- `display: flex`
- `color: color(display-p3 1 0 0)`

## Running Tests

### Command Line

```bash
# Run all fixture tests
pnpm run test:fixtures

# Run specific fixture with config
pnpm exec playwright test --config=test/fixtures-validation/fixture-validation.playwright.config.js --grep "basic-media"

# Run with browser UI (for debugging)
pnpm exec playwright test --config=test/fixtures-validation/fixture-validation.playwright.config.js --ui
```

### Using the Helper Script

```bash
# Run all fixtures
node scripts/validate-fixtures.js

# Run specific fixture
node scripts/validate-fixtures.js basic-media

# List available fixtures
node scripts/validate-fixtures.js --list

# Show help
node scripts/validate-fixtures.js --help
```

## Adding New Fixtures

1. **Create input CSS** with `if()` functions:

```css
/* my-feature.input.css */
.element {
color: if(
supports(color: lab(50% 20 -30)): lab(50% 20 -30) ; else: #blue
);
}
```

2. **Create expected CSS** with the desired output:

```css
/* my-feature.expected.css */
.element {
color: #blue;
}
@supports (color: lab(50% 20 -30)) {
.element {
color: lab(50% 20 -30);
}
}
```

3. **Test automatically** - the validation will pick up new fixtures

## Understanding Test Results

### ✅ Passing Tests

All computed styles match between polyfill and expected CSS.

### ❌ Failing Tests

Common failure reasons:

1. **Style Mismatch**: Polyfill produces different computed values

```text
Property 'width' should match between polyfill and expected CSS
Expected: "400px"
Received: "50%"
```

2. **Media Query Issues**: Responsive behavior doesn't match

```text
Property 'width' should match at mobile viewport (375x667)
```

3. **Polyfill Errors**: JavaScript errors in the polyfill code
```text
Error: if() function parsing failed
```

## Browser Support

The tests run on:

- **Chromium** (primary - matches most real-world usage)
- **Firefox** (cross-browser validation)
- **WebKit** (Safari compatibility)

## Benefits

This validation system provides:

1. **Real Browser Testing** - No mocking, actual CSS computation
2. **Visual Accuracy** - Ensures polyfill produces identical rendering
3. **Regression Detection** - Catches breaking changes automatically
4. **Cross-Browser Validation** - Tests on multiple engines
5. **Responsive Testing** - Validates media query behavior
6. **Feature Detection** - Ensures @supports works correctly

## Troubleshooting

### Tests Won't Start

- Ensure Playwright is installed: `npx playwright install`
- Check that fixtures exist in `test/fixtures/`

### Style Mismatches

- Check if polyfill is correctly transforming CSS
- Verify expected CSS is accurate
- Test manually in browser to confirm behavior

### Performance Issues

- Tests run in parallel by default
- Use `--workers=1` to run sequentially if needed
- Consider reducing viewport testing for faster runs

This fixture validation system gives you confidence that your CSS `if()` polyfill works correctly across different browsers and scenarios, providing the same visual results as native CSS would.
6 changes: 4 additions & 2 deletions docs/TEST_FIXTURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ This document demonstrates the centralized test fixture system that provides a s

```css
.test {
color: if(style(--theme): var(--primary) ; else: #00f);
color: if(style(--theme): var(--primary); else: #00f);
}
```

Expand Down Expand Up @@ -207,7 +207,9 @@ This document demonstrates the centralized test fixture system that provides a s

```css
.responsive {
width: if(media((width >= 768px) and (width <= 1024px)): 50%; else: 100%);
width: if(
media((width >= 768px) and (width <= 1024px)): 50%; else: 100%
);
}
```

Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,18 @@
"prelint:packages": "pnpm run build",
"prepare": "husky",
"preserve": "pnpm --filter=css-if-polyfill run build",
"pretest:fixtures": "npm run build",
"serve": "http-server -p 3000 -o examples/basic-examples.html",
"test": "pnpm --recursive run test"
"test": "pnpm --recursive run test && pnpm run test:fixtures",
"test:fixtures": "playwright test test/fixtures-validation/fixture-validation.test.js --config=test/fixtures-validation/fixture-validation.playwright.config.js"
},
"devDependencies": {
"@babel/core": "7.28.0",
"@babel/preset-env": "7.28.0",
"@changesets/cli": "2.29.5",
"@commitlint/cli": "19.8.1",
"@commitlint/config-conventional": "19.8.1",
"@playwright/test": "^1.54.1",
"@vitest/coverage-v8": "3.2.4",
"http-server": "14.1.1",
"husky": "9.1.7",
Expand Down
12 changes: 12 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

109 changes: 109 additions & 0 deletions scripts/validate-fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env node

/**
* Fixture Test Runner
*
* This script helps validate CSS if() polyfill fixtures by running them through
* a real browser environment using Playwright.
*
* Usage:
* node scripts/validate-fixtures.js [fixture-name]
*
* Examples:
* node scripts/validate-fixtures.js # Run all fixtures
* node scripts/validate-fixtures.js basic-media # Run specific fixture
*/

import { execSync } from 'node:child_process';
import { readdirSync } from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const fixturesDir = path.join(__dirname, '..', 'test', 'fixtures');

function getAvailableFixtures() {
const files = readdirSync(fixturesDir);
const inputFiles = files.filter((file) => file.endsWith('.input.css'));
return inputFiles.map((file) => file.replace('.input.css', ''));
}

function runFixtureTests(fixtureName = null) {
console.log('🎭 CSS if() Polyfill Fixture Validation');
console.log('==========================================\n');

const availableFixtures = getAvailableFixtures();

if (fixtureName) {
if (!availableFixtures.includes(fixtureName)) {
console.error(`❌ Fixture "${fixtureName}" not found.`);
console.log('\nAvailable fixtures:');
for (const name of availableFixtures) console.log(` - ${name}`);
process.exit(1);
}

console.log(`🧪 Running fixture: ${fixtureName}\n`);
} else {
console.log(`🧪 Running all ${availableFixtures.length} fixtures:\n`);
for (const name of availableFixtures) console.log(` ✓ ${name}`);
console.log('');
}

try {
// Run Playwright tests
const grepPattern = fixtureName
? `--grep "validates ${fixtureName} fixture"`
: '';
const command = `npx playwright test test/fixtures-validation/fixture-validation.test.js --config=test/fixtures-validation/fixture-validation.playwright.config.js ${grepPattern}`;

console.log('🚀 Starting browser-based validation...\n');

execSync(command, {
stdio: 'inherit',
cwd: path.join(__dirname, '..')
});

console.log('\n✅ All fixture validations passed!');
console.log('\nThis means:');
console.log(' • Your polyfill correctly transforms input CSS');
console.log(' • Browser rendering matches expected output');
console.log(' • Media queries work responsively');
console.log(' • @supports queries function properly');
} catch {
console.error('\n❌ Fixture validation failed!');
console.error('Please check the test output above for details.');
process.exit(1);
}
}

// Parse command line arguments
const fixtureName = process.argv[2];

// Show help if requested
if (fixtureName === '--help' || fixtureName === '-h') {
console.log('CSS if() Polyfill Fixture Validator\n');
console.log('Usage:');
console.log(' node scripts/validate-fixtures.js [fixture-name]\n');
console.log('Examples:');
console.log(
' node scripts/validate-fixtures.js # Run all fixtures'
);
console.log(
' node scripts/validate-fixtures.js basic-media # Run specific fixture'
);
console.log(
' node scripts/validate-fixtures.js --list # List available fixtures\n'
);
process.exit(0);
}

// List fixtures if requested
if (fixtureName === '--list' || fixtureName === '-l') {
console.log('Available fixtures:');
for (const name of getAvailableFixtures()) console.log(` ${name}`);
process.exit(0);
}

// Run the tests
runFixtureTests(fixtureName);
Loading
Loading