소스 정보
- 저장소
- microsoft/webui
- 최근 소스 활동
- 2026년 10월 12일 17:06
- 감지된 SKILL.md 언어
- 영어
- 스타
- 98
- 포크
- 24
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
SKILL.md 표시 중
SKILL.md
소스 지침 · 읽기 전용 미리보기- name
- testing
- description
- Test patterns, fixture structure, and when to use E2E vs unit tests.
# Testing
Use this skill when writing or modifying tests in this repository.
## Rust crate tests
Unit tests live alongside code in `#[cfg(test)]` modules. Integration tests go in each crate's `tests/` directory.
```bash
cargo test -p microsoft-webui-handler # one crate
cargo test --workspace # all crates
cargo xtask test # via xtask (part of quality gate)
```
Every code change ships with tests:
| Change type | Requirement |
|-------------|-------------|
| New public API | At least one unit test per function/method |
| Bug fix | Regression test that fails without the fix |
| Performance change | Benchmark comparison (before/after) |
| Refactor | Existing tests pass unchanged |
Never remove, weaken, or `#[ignore]` an existing test unless explicitly asked.
## WebUI Node package tests
`cargo xtask test` builds the host native addon and the WebUI TypeScript package,
then runs `pnpm --filter @microsoft/webui test` with `WEBUI_ADDON_PATH` set to the
artifact reported by Cargo. This includes the projection compiler, adapter,
class-factory, parser, and native build/render regressions in both the local
`cargo xtask check` gate and the PR Test job. Browser suites run separately via
`cargo xtask e2e`.
## Shared test utilities (`webui-test-utils`)
The `webui-test-utils` crate provides common Rust test helpers, builders, and fixtures. Check it before writing new helpers - avoid duplicating across crates.
```toml
[dev-dependencies]
webui-test-utils = { path = "../webui-test-utils" }
```
## webui-framework E2E fixtures
The `packages/webui-framework` package uses Playwright for E2E tests. Each fixture is a mini WebUI app compiled and rendered by the real pipeline.
See `tests/fixtures/README.md` for the full reference.
### Fixture structure
```
tests/fixtures/<name>/
src/
index.html Page template (uses the component)
<tag-name>/
<tag-name>.html Component template (real WebUI syntax)
<tag-name>.css Component CSS (optional)
state.json Initial render state (all bound properties)
element.ts Component class (NO template registration)
<name>.spec.ts Playwright tests
webui.config.json Build options override (optional)
```
### How it works
The test server (`fixture-render.ts`) auto-discovers fixtures with `src/index.html`, calls `@microsoft/webui` `build()` + `render()` to produce SSR HTML with template IIFEs, hydration markers, and inventory. The result is served at `/<name>/fixture.html`.
### Creating a new fixture
1. Create `fixtures/<name>/src/index.html` — page template:
```html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>My Fixture</title></head>
<body>
<test-widget label="Hello"></test-widget>
</body>
</html>
```
2. Create `fixtures/<name>/src/test-widget/test-widget.html` — component template:
```html
<span class="label">{{label}}</span>
<span class="count">{{count}}</span>
<button class="inc" @click="{increment()}">+</button>
```
3. Create `fixtures/<name>/state.json` — initial state:
```json
{ "label": "Hello", "count": 0 }
```
4. Create `fixtures/<name>/element.ts` — component class only:
```typescript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { WebUIElement, attr, observable } from '../../../src/index.js';
export class TestWidget extends WebUIElement {
@attr label = 'Hello';
@observable count = 0;
increment(): void {
this.count += 1;
}
}
TestWidget.define('test-widget');
```
5. Create `fixtures/<name>/<name>.spec.ts` — Playwright tests:
```typescript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { expect, test } from '@playwright/test';
test.describe('widget fixture', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/<name>/fixture.html');
await page.waitForFunction(() => {
const el = document.querySelector('test-widget');
return el && (el as any).$ready === true;
});
});
test('renders SSR content', async ({ page }) => {
await expect(page.locator('test-widget .label')).toHaveText('Hello');
await expect(page.locator('test-widget .count')).toHaveText('0');
});
test('updates on click', async ({ page }) => {
await page.locator('test-widget .inc').click();
await expect(page.locator('test-widget .count')).toHaveText('1');
});
});
```
### Template syntax quick reference
| Feature | Syntax |
|---------|--------|
| Text binding | `{{propertyName}}` |
| Event binding | `@click="{handler()}"` or `@click="{handler(e)}"` |
| Boolean attribute | `?disabled="{{isDisabled}}"` |
| Dynamic attribute | `href="{{url}}"` |
| Mixed attribute | `href="/items/{{id}}"` |
| Complex property | `:items="{{items}}"` |
| Element ref | `w-ref="{myElement}"` |
| Conditional | `<if condition="show">...</if>` |
| Negation | `<if condition="!hidden">...</if>` |
| Comparison | `<if condition="count > 0">...</if>` |
| Compound | `<if condition="active && !busy">...</if>` |
| Loop | `<for each="item in items">...</for>` |
| Nested loop | `<for each="group in groups">...<for each="item in group.items">...</for></for>` |
| Shadow DOM | `<template shadowrootmode="open">...</template>` |
| Root event | `<template shadowrootmode="open" @click="{handler(e)}">` |
| Slot | `<slot></slot>` |
### Dynamic children pattern
Components only created via `document.createElement()` (not in any template) need a false `<if>` in the page template to make their template IIFEs available:
```html
<body>
<my-host></my-host>
<if condition="showChild"><my-child></my-child></if>
</body>
```
With `state.json`: `{ "showChild": false }`.
### Per-fixture build config
Create `webui.config.json` to override build options:
```json
{ "css": "module" }
```
### Light and Shadow fixtures
Ordinary unwrapped fixture components exercise the Shadow default. Set
`"dom": "light"` in `webui.config.json` to exercise global Light rendering; a
fixture component requiring Shadow inside that build must author a sole
top-level `<template shadowrootmode="open">`. A sole bare top-level
`<template>` explicitly exercises Light even in the default Shadow fixture.
See `fixtures/dom-default/` and
`fixtures/light-dom/` for both paths through the real pipeline. Reserve manual
`registerCompiledTemplate` fixtures for raw template-metadata scenarios.
### Running framework E2E tests
```bash
cd packages/webui-framework
pnpm build # build the framework
pnpm test # unit + E2E tests
npx playwright test # E2E only
npx playwright test tests/fixtures/<name>/ # one fixture
```
### Test support package (`@microsoft/webui-test-support`)
The `packages/webui-test-support` package provides:
- **`registerCompiledTemplate(name, meta)`** — register a raw `TemplateMeta` object (for manual/light-DOM fixtures)
- **`renderTemplateScript(name, meta)`** — render a template as an inline `<script>` tag
- **`renderFixtures({ fixturesRoot })`** — build+render all pipeline fixtures
- **`buildFixtureEntries({ ... })`** — bundle element.ts files via esbuild
- **`startFixtureServer({ ... })`** — start the HTTP fixture server
## When to use what
| Scenario | Tool |
|----------|------|
| Rust crate logic (parsing, rendering, state, expressions) | `#[test]` unit tests |
| Rust crate integration (full pipeline) | `tests/` integration tests |
| Rust performance | `cargo bench` with Criterion |
| Framework hydration, reactive updates, DOM behavior | Playwright E2E fixtures |
| Router navigation, lazy loading, chain diffing | Playwright E2E fixtures |
| Template metadata encoding/decoding | Node unit tests (`pnpm test:unit`) |
GitHub에서 보기