This commit is contained in:
John Kreitlow 2024-01-19 12:26:00 -08:00
Родитель d9f4a6c246
Коммит a4018373f2
95 изменённых файлов: 10186 добавлений и 7099 удалений

Просмотреть файл

@ -1,12 +1,20 @@
{
"extends": ["@microsoft/eslint-config-fast-dna", "prettier"],
"extends": [
"@microsoft/eslint-config-fast-dna",
"prettier",
"plugin:storybook/recommended"
],
"rules": {
"@typescript-eslint/class-name-casing": "off",
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "typeLike",
"format": ["UPPER_CASE", "camelCase", "PascalCase"],
"format": [
"UPPER_CASE",
"camelCase",
"PascalCase"
],
"leadingUnderscore": "allow"
}
],
@ -24,13 +32,22 @@
},
"overrides": [
{
"files": ["**/*.ts"],
"excludedFiles": ["**/*.stories.ts"],
"files": [
"**/*.ts"
],
"excludedFiles": [
"**/*.stories.ts",
"**/__test__/**",
"test/**"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": ["**/stories/**", "**/*.pw.spec.ts"]
"patterns": [
"**/stories/**",
"**/*.pw.spec.ts"
]
}
]
}

Просмотреть файл

@ -1,60 +0,0 @@
const ResolveTypescriptPlugin = require("resolve-typescript-plugin");
const path = require("path");
module.exports = {
addons: ["@storybook/addon-links", "@storybook/addon-essentials"],
stories: ["../src/**/stories/*.stories.ts", "debug.stories.ts"],
framework: "@storybook/html",
features: {
babelModeV7: true,
},
core: {
disableTelemetry: true,
builder: {
name: "webpack5",
options: {
lazyCompilation: true,
fsCache: true,
},
},
},
webpackFinal: async config => {
config.performance = {
...(config.performance ?? {}),
hints: false,
};
config.module.rules = [
{
test: /\.svg$/,
loader: "svg-inline-loader",
options: {
removeSVGTagAttrs: false,
},
},
{
test: /\.ts$/,
loader: "ts-loader",
sideEffects: true,
options: {
configFile: path.resolve("./tsconfig.json"),
transpileOnly: true,
},
},
{
test: /\.m?js$/,
enforce: "pre",
loader: require.resolve("source-map-loader"),
resolve: {
fullySpecified: false,
},
},
];
config.resolve.plugins = [
...(config.resolve.plugins ?? []),
new ResolveTypescriptPlugin({
includeNodeModules: true,
}),
];
return config;
},
};

Просмотреть файл

@ -0,0 +1,33 @@
import type { StorybookConfig } from "@storybook/html-vite";
import { dirname, join } from "path";
import { mergeConfig } from "vite";
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, "package.json")));
}
const config: StorybookConfig = {
core: { disableTelemetry: true },
stories: ["../src/**/*.stories.ts"],
docs: { autodocs: true },
addons: [
// getAbsolutePath("@storybook/addon-interactions"),
getAbsolutePath("@storybook/addon-essentials"),
],
framework: {
name: getAbsolutePath("@storybook/html-vite"),
options: {},
},
async viteFinal(config) {
// Merge custom configuration into the default config
return mergeConfig(config, {
resolve: {
alias: [{ find: /^(.*\.svg)$/, replacement: "$1?raw" }],
},
});
},
};
export default config;

Просмотреть файл

@ -1,5 +0,0 @@
import { addons } from "@storybook/addons";
addons.setConfig({
enableShortcuts: false,
});

Просмотреть файл

@ -1,24 +1,31 @@
<svg style="display: none;">
<symbol
id="test-icon"
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<svg style="display: none">
<symbol id="test-icon" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 5.5a1 1 0 100 2 1 1 0 000-2zm-5 1a1 1 0 112 0 1 1 0 01-2 0zm3.5-4a.5.5 0 00-1 0V3h-3C5.67 3 5 3.67 5 4.5v4c0 .83.67 1.5 1.5 1.5h7c.83 0 1.5-.67 1.5-1.5v-4c0-.83-.67-1.5-1.5-1.5h-3v-.5zM6.5 4h7c.28 0 .5.22.5.5v4a.5.5 0 01-.5.5h-7a.5.5 0 01-.5-.5v-4c0-.28.22-.5.5-.5zm3.75 14c2.62-.04 4.2-.6 5.12-1.44A3.52 3.52 0 0016.5 14h.01v-.69c0-1-.81-1.8-1.8-1.8h-3.2v-.01H5.3c-.99 0-1.8.81-1.8 1.81v.7c.04.77.25 1.75 1.13 2.55.93.84 2.5 1.4 5.12 1.44h.5zm-4.94-5.5h9.38c.45 0 .81.37.81.81v.44c0 .69-.13 1.46-.8 2.07C14 16.45 12.66 17 10 17s-4.01-.55-4.7-1.18a2.63 2.63 0 01-.8-2.07v-.44c0-.44.36-.8.8-.8z"
/>
</symbol>
<symbol
id="test-icon-2"
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<symbol id="test-icon-2" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.26 4.6a5.21 5.21 0 0 1 9.03 5.22l-.2.34a.5.5 0 0 1-.67.19l-3.47-2-1.93 3.38c1.34.4 2.5 1.33 3.31 2.52h-.09c-.34 0-.66.11-.92.31A4.9 4.9 0 0 0 9.5 12.5a4.9 4.9 0 0 0-3.82 2.06 1.5 1.5 0 0 0-1.01-.3 5.94 5.94 0 0 1 5.31-2.74l2.1-3.68-3.83-2.2a.5.5 0 0 1-.18-.7l.2-.33Zm.92.42 1.7.98.02-.02a8.08 8.08 0 0 1 3.27-2.74 4.22 4.22 0 0 0-4.99 1.78ZM14 7.8c.47-.82.7-1.46.77-2.09a5.8 5.8 0 0 0-.06-1.62 6.96 6.96 0 0 0-2.95 2.41L14 7.8Zm.87.5 1.61.93a4.22 4.22 0 0 0-.74-5.02c.07.56.09 1.1.02 1.63-.1.79-.38 1.56-.89 2.46Zm-9.63 7.3a.5.5 0 0 0-.96.03c-.17.7-.5 1.08-.86 1.3-.38.23-.87.32-1.42.32a.5.5 0 0 0 0 1c.64 0 1.33-.1 1.94-.47.34-.2.64-.5.88-.87a2.96 2.96 0 0 0 4.68-.01 2.96 2.96 0 0 0 4.74-.06c.64.9 1.7 1.41 2.76 1.41a.5.5 0 1 0 0-1c-.98 0-1.96-.64-2.29-1.65a.5.5 0 0 0-.95 0 1.98 1.98 0 0 1-3.79.07.5.5 0 0 0-.94 0 1.98 1.98 0 0 1-3.8-.08Z"
/>
</symbol>
<symbol
id="chevron-up-12-regular"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.15 7.35c.2.2.5.2.7 0L6 4.21l3.15 3.14a.5.5 0 1 0 .7-.7l-3.5-3.5a.5.5 0 0 0-.7 0l-3.5 3.5a.5.5 0 0 0 0 .7Z"
/>
</symbol>
<symbol
id="chevron-down-12-regular"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.85 4.65a.5.5 0 0 0-.7 0L6 7.79 2.85 4.65a.5.5 0 0 0-.7.7l3.5 3.5a.5.5 0 0 0 .7 0l3.5-3.5a.5.5 0 0 0 0-.7Z"
/>
</symbol>
</svg>

До

Ширина:  |  Высота:  |  Размер: 1.8 KiB

После

Ширина:  |  Высота:  |  Размер: 2.3 KiB

Просмотреть файл

@ -0,0 +1,90 @@
import { useArgs } from "@storybook/client-api";
import type { Preview } from "@storybook/html";
import customElements from "../dist/custom-elements.json";
global["__STORYBOOK_CUSTOM_ELEMENTS_MANIFEST__"] = customElements;
import "@microsoft/fast-element";
import "../src/anchor/stories/anchor.register.js";
import "../src/anchored-region/stories/anchored-region.register.js";
import "../src/avatar/stories/avatar.register.js";
import "../src/badge/stories/badge.register.js";
import "../src/button/stories/button.register.js";
import "../src/card/stories/card.register.js";
import "../src/checkbox/stories/checkbox.register.js";
import "../src/dialog/stories/dialog.register.js";
import "../src/disclosure/stories/disclosure.register.js";
import "../src/divider/stories/divider.register.js";
import "../src/flipper/stories/flipper.register.js";
import "../src/form-associated/stories/form-associated.register.js";
import "../src/number-field/stories/number-field.register.js";
import "../src/picker/stories/picker.register.js";
import "../src/progress-ring/stories/progress-ring.register.js";
import "../src/progress/stories/progress.register.js";
import "../src/radio/stories/radio.register.js";
import "../src/search/stories/search.register.js";
import "../src/skeleton/stories/skeleton.register.js";
import "../src/switch/stories/switch.register.js";
import "../src/text-area/stories/text-area.register.js";
import "../src/text-field/stories/text-field.register.js";
import "../src/tooltip/stories/tooltip.register.js";
import "../src/data-grid/stories/data-grid-cell.register.js";
import "../src/data-grid/stories/data-grid-row.register.js";
import "../src/data-grid/stories/data-grid.register.js";
import "../src/calendar/stories/calendar.register.js";
import "../src/slider-label/stories/slider-label.register.js";
import "../src/slider/stories/slider.register.js";
import "../src/accordion-item/stories/accordion-item.register.js";
import "../src/accordion/stories/accordion.register.js";
import "../src/breadcrumb-item/stories/breadcrumb-item.register.js";
import "../src/breadcrumb/stories/breadcrumb.register.js";
import "../src/listbox-option/stories/listbox-option.register.js";
import "../src/combobox/stories/combobox.register.js";
import "../src/listbox/stories/listbox.register.js";
import "../src/select/stories/select.register.js";
import "../src/tab-panel/stories/tab-panel.register.js";
import "../src/tab/stories/tab.register.js";
import "../src/tabs/stories/tabs.register.js";
import "../src/horizontal-scroll/stories/horizontal-scroll.register.js";
import "../src/radio-group/stories/radio-group.register.js";
import "../src/toolbar/stories/toolbar.register.js";
import "../src/menu-item/stories/menu-item.register.js";
import "../src/menu/stories/menu.register.js";
import "../src/tree-item/stories/tree-item.register.js";
import "../src/tree-view/stories/tree-view.register.js";
import { FAST, html } from "@microsoft/fast-element";
FAST["html"] = html;
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^.*Handler" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
decorators: [
(Story, context) => {
const [_, updateArgs] = useArgs();
return Story({ ...context, updateArgs });
},
],
};
export default preview;

Просмотреть файл

@ -12,7 +12,7 @@ export default {
"src/**/*.md",
"src/**/*.spec.ts",
"src/**/index.ts",
"src/**/stories/*",
"src/**/stories/*"
],
/** Directory to output CEM to */
outdir: "dist",

Просмотреть файл

@ -412,7 +412,7 @@ export const DayFormat: {
// @public
export type DayFormat = ValuesOf<typeof DayFormat>;
// @public (undocumented)
// @public
export const defaultCellFocusTargetCallback: (cell: FASTDataGridCell) => HTMLElement | null;
// Warning: (ae-different-release-tags) This symbol has another declaration with a different release tag
@ -2067,6 +2067,9 @@ export class FASTTextField extends FormAssociatedTextField {
size: number;
// (undocumented)
protected sizeChanged(): void;
slottedDataList?: HTMLDataListElement[];
// (undocumented)
protected slottedDataListChanged(prev: HTMLDataListElement[] | undefined, next: HTMLDataListElement[] | undefined): void;
spellcheck: boolean;
// (undocumented)
protected spellcheckChanged(): void;
@ -2870,9 +2873,9 @@ export type YearFormat = ValuesOf<typeof YearFormat>;
// Warnings were encountered during analysis:
//
// dist/dts/calendar/calendar.d.ts:51:5 - (ae-incompatible-release-tags) The symbol "dataGridCell" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/calendar/calendar.d.ts:52:5 - (ae-incompatible-release-tags) The symbol "dataGridRow" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/calendar/calendar.d.ts:53:5 - (ae-incompatible-release-tags) The symbol "dataGrid" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/calendar/calendar.d.ts:52:5 - (ae-incompatible-release-tags) The symbol "dataGridCell" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/calendar/calendar.d.ts:53:5 - (ae-incompatible-release-tags) The symbol "dataGridRow" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/calendar/calendar.d.ts:54:5 - (ae-incompatible-release-tags) The symbol "dataGrid" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/data-grid/data-grid-row.template.d.ts:9:5 - (ae-incompatible-release-tags) The symbol "dataGridCell" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/data-grid/data-grid.template.d.ts:9:5 - (ae-incompatible-release-tags) The symbol "dataGridRow" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta
// dist/dts/picker/picker.template.d.ts:9:5 - (ae-incompatible-release-tags) The symbol "anchoredRegion" is marked as @public, but its signature references "TemplateElementDependency" which is marked as @beta

Просмотреть файл

@ -226,7 +226,7 @@
"doc": "api-extractor run --local",
"doc:ci": "api-extractor run",
"build:rollup": "rollup -c",
"build:storybook": "build-storybook",
"build:storybook": "storybook build",
"build:tsc": "tsc -p ./tsconfig.build.json",
"prebuild": "yarn build:tsc",
"build": "yarn build:rollup",
@ -242,11 +242,14 @@
"prettier:diff": "prettier --config ../../../.prettierrc \"**/*.ts\" --list-different",
"eslint": "eslint . --ext .ts",
"eslint:fix": "eslint . --ext .ts --fix",
"pretest": "yarn eslint && yarn build-storybook --quiet",
"pretest": "yarn eslint",
"test": "playwright test",
"posttest:ci": "yarn doc:ci",
"start": "yarn start:storybook",
"start:storybook": "start-storybook -p 6006"
"test:dev": "node test/server.js",
"watch:dev": "concurrently \"yarn:test:dev\" \"yarn:watch:rollup\"",
"watch:rollup": "rollup -c -w --waitForBundleInput",
"start:storybook": "storybook dev -p 6006"
},
"devDependencies": {
"@custom-elements-manifest/analyzer": "^0.5.7",
@ -255,35 +258,41 @@
"@microsoft/tsdoc-config": "^0.13.4",
"@playwright/test": "^1.25.2",
"@rollup/plugin-node-resolve": "^13.3.0",
"@storybook/addon-docs": "6.5.10",
"@storybook/addon-essentials": "6.5.10",
"@storybook/addon-links": "6.5.10",
"@storybook/builder-webpack5": "6.5.10",
"@storybook/csf": "0.0.2--canary.0899bb7.0",
"@storybook/html": "6.5.10",
"@storybook/manager-webpack5": "6.5.10",
"concurrently": "^7.3.0",
"@storybook/addon-essentials": "^7.6.8",
"@storybook/addon-interactions": "^7.6.8",
"@storybook/addon-links": "^7.6.8",
"@storybook/blocks": "^7.6.8",
"@storybook/html": "^7.6.8",
"@storybook/html-vite": "^7.6.8",
"@storybook/test": "^7.6.8",
"concurrently": "^8.2.2",
"eslint-plugin-storybook": "^0.6.15",
"esm": "^3.2.25",
"express": "^4.18.1",
"expect": "29.2.1",
"express": "^4.18.1",
"prettier": "2.8.8",
"qs": "^6.11.0",
"qs": "^6.11.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"resolve-typescript-plugin": "^1.2.0",
"rollup-plugin-filesize": "^9.1.2",
"rollup": "^4.9.1",
"rollup-plugin-filesize": "^10.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-transform-tagged-template": "^0.0.3",
"rollup": "^2.71.1",
"source-map": "^0.7.4",
"source-map-loader": "^0.2.4",
"source-map": "^0.7.3",
"storybook": "^7.6.8",
"svg-inline-loader": "^0.8.2",
"rollup-plugin-inline-svg": "^3.0.3",
"@rollup/plugin-typescript": "^11.1.5",
"ts-loader": "^7.0.2",
"ts-node": "^8.9.1",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.7.0",
"vite": "^5.0.10",
"wait-on": "^6.0.1"
},
"dependencies": {
"@floating-ui/dom": "^1.0.3",
"@floating-ui/dom": "1.0.3",
"@microsoft/fast-element": "2.0.0-beta.26",
"@microsoft/fast-web-utilities": "^6.0.0",
"tabbable": "^5.2.0",

Просмотреть файл

@ -1,25 +1,26 @@
import type { Locator, PlaywrightTestConfig } from "@playwright/test";
import { expect } from "@playwright/test";
const isCI = !!process.env.CI;
const config: PlaywrightTestConfig = {
projects: [{ name: "chromium" }, { name: "firefox" }, { name: "webkit" }],
projects: [{ name: "chromium" }],
reporter: "list",
testMatch: /.*\.pw\.spec\.ts$/,
retries: 3,
fullyParallel: process.env.CI ? false : true,
timeout: process.env.CI ? 10000 : 30000,
fullyParallel: !isCI,
timeout: isCI ? 10000 : 30000,
use: {
baseURL: "http://localhost:6006/iframe.html",
baseURL: "http://localhost:6006/",
viewport: {
height: 1280,
width: 720,
},
},
webServer: {
// double-quotes are required for Windows
command: `node -e "import('express').then(({ default: e }) => e().use(e.static('./storybook-static')).listen(6006))"`,
port: 6006,
reuseExistingServer: process.env.CI ? false : true,
command: "node test/server.js",
port: 6007,
reuseExistingServer: !isCI,
},
};

Просмотреть файл

@ -1,28 +1,11 @@
import filesize from "rollup-plugin-filesize";
import { nodeResolve } from "@rollup/plugin-node-resolve";
import filesize from "rollup-plugin-filesize";
import { terser } from "rollup-plugin-terser";
import transformTaggedTemplate from "rollup-plugin-transform-tagged-template";
import {
transformCSSFragment,
transformHTMLFragment,
} from "../../../build/transform-fragments.js";
const parserOptions = {
sourceType: "module",
};
import inlineSvg from "rollup-plugin-inline-svg";
import typescript from "@rollup/plugin-typescript";
const plugins = [
nodeResolve(),
transformTaggedTemplate({
tagsToProcess: ["css"],
transformer: transformCSSFragment,
parserOptions,
}),
transformTaggedTemplate({
tagsToProcess: ["child", "html", "item"],
transformer: transformHTMLFragment,
parserOptions,
}),
filesize({
showMinifiedSize: false,
showBrotliSize: true,
@ -60,4 +43,19 @@ export default [
],
plugins,
},
{
input: "test/bundle.ts",
output: {
file: "test/public/dist/bundle.js",
format: "cjs",
},
context: "window",
plugins: [
nodeResolve(),
inlineSvg(),
typescript({
tsconfig: "./tsconfig.test.json",
}),
],
},
];

Просмотреть файл

@ -0,0 +1 @@
/// <reference types="vite/client" />

Просмотреть файл

@ -8,8 +8,3 @@ declare global {
}
export {};
declare module "*.svg" {
const content: any;
export default content;
}

Просмотреть файл

@ -4,42 +4,9 @@ import type {
Args,
ComponentAnnotations,
StoryAnnotations,
StoryContext,
StoryContext
} from "@storybook/csf";
import qs from "qs";
/**
* Returns a formatted URL for a given Storybook fixture.
*
* @param id - the Storybook fixture ID
* @param args - Story args
* @returns - the local URL for the Storybook fixture iframe
*/
export function fixtureURL(
id: string = "debug--blank",
args?: Record<string, any>
): string {
const params: Record<string, any> = { id };
if (args) {
params.args = qs
.stringify(args, {
allowDots: true,
delimiter: ";",
format: "RFC1738",
encode: false,
})
.replace(/=/g, ":")
.replace(/\//g, "--");
}
const url = qs.stringify(params, {
addQueryPrefix: true,
format: "RFC1738",
encode: false,
});
return url;
}
import type { HtmlRenderer } from "@storybook/html";
/**
* A helper that returns a function to bind a Storybook story to a ViewTemplate.
@ -63,23 +30,23 @@ export function renderComponent<TArgs = Args>(
/**
* A helper that returns a function to bind a Storybook story to a ViewTemplate.
*/
export type FASTFramework = {
component: typeof FASTElement;
storyResult: FASTElement | Element | DocumentFragment;
export type FASTFramework = HtmlRenderer & {
component: string;
storyResult: HtmlRenderer["storyResult"] | FASTElement;
};
/**
* Metadata to configure the stories for a component.
*/
export type Meta<TArgs = Args> = ComponentAnnotations<
export type Meta<TArgs extends Args = Args> = ComponentAnnotations<
FASTFramework,
Omit<TArgs, keyof FASTElement>
Omit<TArgs, keyof FASTElement> & Args
>;
/**
* Story function that represents a CSFv3 component example.
*/
export declare type StoryObj<TArgs = Args> = StoryAnnotations<FASTFramework, TArgs>;
export declare type StoryObj<TArgs extends Args = Args> = StoryAnnotations<FASTFramework, TArgs>;
/**
* Story function that represents a CSFv2 component example.
@ -91,7 +58,7 @@ export declare type StoryFn<TArgs = Args> = AnnotatedStoryFn<FASTFramework, TArg
*
* NOTE that in Storybook 7.0, this type will be renamed to `StoryFn` and replaced by the current `StoryObj` type.
*/
export declare type Story<TArgs = Args> = StoryFn<StoryArgs<TArgs>>;
export declare type Story<TArgs = Args> = StoryObj<StoryArgs<TArgs>>;
/**
* Combined Storybook story args.

Просмотреть файл

@ -1,35 +1,15 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTAccordionItem } from "./accordion-item.js";
test.describe("Accordion item", () => {
let page: Page;
let element: Locator;
let root: Locator;
let heading: Locator;
let button: Locator;
test("should set a default heading level of 2 when `headinglevel` is not provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-accordion-item");
element = page.locator("fast-accordion-item");
root = page.locator("#root");
heading = page.locator(`[role="heading"]`);
button = element.locator("button");
await page.goto(fixtureURL("accordion-item--accordion-item"));
});
test.afterAll(async () => {
await page.close();
});
test("should set a default heading level of 2 when `headinglevel` is not provided", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion-item>
<span slot="heading">Heading 1</span>
@ -43,8 +23,16 @@ test.describe("Accordion item", () => {
await expect(element).toHaveJSProperty("headinglevel", 2);
});
test("should set the `aria-level` attribute on the internal heading element equal to the heading level", async () => {
await root.evaluate(node => {
test("should set the `aria-level` attribute on the internal heading element equal to the heading level", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-accordion-item");
const heading = element.locator(`[role="heading"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion-item>
<span slot="heading">Heading 1</span>
@ -60,8 +48,16 @@ test.describe("Accordion item", () => {
await expect(heading).toHaveAttribute("aria-level", "3");
});
test("should set `aria-expanded` property on the internal control equal to the `expanded` property", async () => {
await root.evaluate(node => {
test("should set `aria-expanded` property on the internal control equal to the `expanded` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-accordion-item");
const button = element.locator("button");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion-item expanded></fast-accordion-item>
`;
@ -76,8 +72,16 @@ test.describe("Accordion item", () => {
await expect(button).toHaveAttribute("aria-expanded", "false");
});
test("should set `disabled` attribute on the internal control equal to the `disabled` property", async () => {
await root.evaluate(node => {
test("should set `disabled` attribute on the internal control equal to the `disabled` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-accordion-item");
const button = element.locator("button");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion-item disabled></fast-accordion-item>
`;
@ -92,8 +96,16 @@ test.describe("Accordion item", () => {
await expect(button).not.toHaveBooleanAttribute("disabled");
});
test("should set internal properties to match the id when provided", async () => {
await root.evaluate(node => {
test("should set internal properties to match the id when provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-accordion-item");
const button = element.locator("button");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion-item id="foo"></fast-accordion-item>
`;

Просмотреть файл

@ -1,5 +1,6 @@
import { html } from "@microsoft/fast-element";
import type { Meta, Story, StoryArgs } from "../../__test__/helpers.js";
import type { Meta } from "@storybook/html";
import type { Story, StoryArgs } from "../../__test__/helpers.js";
import { renderComponent } from "../../__test__/helpers.js";
import type { FASTAccordionItem } from "../accordion-item.js";
@ -13,18 +14,20 @@ const storyTemplate = html<StoryArgs<FASTAccordionItem>>`
</fast-accordion-item>
`;
export default {
const meta: Meta = {
title: "Accordion Item",
args: {
expanded: false,
},
component: "FASTAccordionItem",
parameters: { actions: { argTypesRegex: "^.*Handler" } },
argTypes: {
expanded: { control: "boolean" },
headinglevel: { control: { type: "number", max: 6, min: 1 } },
id: { control: "text" },
storyContent: { table: { disable: true } },
clickHandler: { action: "click" },
},
} as Meta<FASTAccordionItem>;
};
export default meta;
export const AccordionItem: Story<FASTAccordionItem> = renderComponent(
storyTemplate

Просмотреть файл

@ -1,30 +1,17 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import { AccordionExpandMode } from "./accordion.options.js";
import type { FASTAccordion } from "./accordion.js";
test.describe("Accordion", () => {
let page: Page;
let element: Locator;
let root: Locator;
// test.beforeEach(async ({ page }) => {
// // await page.goto();
// });
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
test("should set an expand mode of `multi` when passed to the `expand-mode` attribute", async ({
page,
}) => {
const element = page.locator("fast-accordion");
element = page.locator("fast-accordion");
root = page.locator("#root");
await page.goto(fixtureURL("accordion--accordion"));
});
test.afterAll(async () => {
await page.close();
});
test("should set an expand mode of `multi` when passed to the `expand-mode` attribute", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="multi">
<fast-accordion-item>
@ -42,8 +29,12 @@ test.describe("Accordion", () => {
await expect(element).toHaveAttribute("expand-mode", AccordionExpandMode.multi);
});
test("should set an expand mode of `single` when passed to the `expand-mode` attribute", async () => {
await root.evaluate(node => {
test("should set an expand mode of `single` when passed to the `expand-mode` attribute", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -61,8 +52,12 @@ test.describe("Accordion", () => {
await expect(element).toHaveAttribute("expand-mode", AccordionExpandMode.single);
});
test("should set a default expand mode of `multi` when `expand-mode` attribute is not passed", async () => {
await root.evaluate(node => {
test("should set a default expand mode of `multi` when `expand-mode` attribute is not passed", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion>
<fast-accordion-item>
@ -82,8 +77,10 @@ test.describe("Accordion", () => {
await expect(element).toHaveAttribute("expand-mode", AccordionExpandMode.multi);
});
test("should expand/collapse items when clicked in multi mode", async () => {
await root.evaluate(node => {
test("should expand/collapse items when clicked in multi mode", async ({ page }) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="multi">
<fast-accordion-item>
@ -109,8 +106,10 @@ test.describe("Accordion", () => {
await expect(items.nth(1)).toHaveAttribute("expanded", "");
});
test("should only have one expanded item in single mode", async () => {
await root.evaluate(node => {
test("should only have one expanded item in single mode", async ({ page }) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -150,8 +149,12 @@ test.describe("Accordion", () => {
await expect(secondItem).toHaveBooleanAttribute("expanded");
});
test("should set the expanded items' button to aria-disabled when in single expand mode", async () => {
await root.evaluate(node => {
test("should set the expanded items' button to aria-disabled when in single expand mode", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -202,8 +205,12 @@ test.describe("Accordion", () => {
);
});
test("should remove an expanded items' expandbutton aria-disabled attribute when expand mode changes from single to multi", async () => {
await root.evaluate(node => {
test("should remove an expanded items' expandbutton aria-disabled attribute when expand mode changes from single to multi", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -238,8 +245,12 @@ test.describe("Accordion", () => {
await expect(firstItem.locator("button")).not.hasAttribute("aria-disabled");
});
test("should set the first item as expanded if no child is expanded by default in single mode", async () => {
await root.evaluate(node => {
test("should set the first item as expanded if no child is expanded by default in single mode", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -271,8 +282,12 @@ test.describe("Accordion", () => {
await expect(secondItem).toHaveBooleanAttribute("expanded");
});
test("should set the first item with an expanded attribute to expanded in single mode", async () => {
await root.evaluate(node => {
test("should set the first item with an expanded attribute to expanded in single mode", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -306,9 +321,14 @@ test.describe("Accordion", () => {
await expect(thirdItem).not.toHaveBooleanAttribute("expanded");
});
test("should allow disabled items to be expanded when in single mode", async () => {
test("should allow disabled items to be expanded when in single mode", async ({
page,
}) => {
test.slow();
await root.evaluate(node => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>
@ -352,8 +372,12 @@ test.describe("Accordion", () => {
await expect(thirdItem).not.toHaveBooleanAttribute("expanded");
});
test("should ignore `change` events from components other than accordion items", async () => {
await root.evaluate(node => {
test("should ignore `change` events from components other than accordion items", async ({
page,
}) => {
const element = page.locator("fast-accordion");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-accordion expand-mode="single">
<fast-accordion-item>

Просмотреть файл

@ -6,7 +6,7 @@ const styles = css`
:host {
box-sizing: border-box;
flex-direction: column;
font: var(--type-ramp-minus1-font-size) / var(--type-ramp-minus1-line-height)
font: var(--type-ramp-minus-1-font-size) / var(--type-ramp-minus1-line-height)
var(--body-font);
color: var(--neutral-foreground-rest);
border-top: calc(var(--stroke-width) * 1px) solid

Просмотреть файл

@ -1,7 +1,7 @@
import { html } from "@microsoft/fast-element";
import type { Meta, Story, StoryArgs } from "../../__test__/helpers.js";
import { renderComponent } from "../../__test__/helpers.js";
import { FASTAccordion } from "../accordion.js";
import type { FASTAccordion } from "../accordion.js";
import { AccordionExpandMode } from "../accordion.options.js";
const storyTemplate = html<StoryArgs<FASTAccordion>>`
@ -10,9 +10,9 @@ const storyTemplate = html<StoryArgs<FASTAccordion>>`
</fast-accordion>
`;
export default {
const meta: Meta<FASTAccordion> = {
title: "Accordion",
component: FASTAccordion,
component: "anchor",
args: {
expandmode: AccordionExpandMode.multi,
},
@ -20,7 +20,9 @@ export default {
expandmode: { control: "select", options: Object.values(AccordionExpandMode) },
storyContent: { table: { disable: true } },
},
} as Meta<FASTAccordion>;
};
export default meta;
export const Accordion: Story<FASTAccordion> = renderComponent(storyTemplate).bind({});
Accordion.args = {

Просмотреть файл

@ -1,24 +1,7 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
test.describe("Anchor", () => {
let page: Page;
let element: Locator;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
element = page.locator("fast-anchor");
await page.goto(fixtureURL("anchor", attributes));
});
test.afterAll(async () => {
await page.close();
});
const attributes = {
href: "href",
ping: "ping",
@ -52,7 +35,22 @@ test.describe("Anchor", () => {
for (const [attribute, value] of Object.entries(attributes)) {
const attributeSpinalCase = spinalCase(attribute);
test(`should set the \`${attributeSpinalCase}\` attribute to \`${value}\` on the internal control`, async () => {
test(`should set the \`${attributeSpinalCase}\` attribute to \`${value}\` on the internal control`, async ({
page,
}) => {
const element = page.locator("fast-anchor");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(root, [attribute, value]) => {
const anchor = document.createElement("fast-anchor");
anchor[attribute] = value;
root.append(anchor);
},
[attribute, value]
);
await expect(element).toHaveAttribute(attributeSpinalCase, `${value}`);
});
}

Просмотреть файл

@ -12,7 +12,7 @@ export type AnchorOptions = StartEndOptions<FASTAnchor>;
/**
* An Anchor Custom HTML Element.
* Based largely on the {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a | <a> element }.
* Based largely on the {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a | `<a>` element }.
*
* @slot start - Content which can be provided before the anchor content
* @slot end - Content which can be provided after the anchor content
@ -27,7 +27,7 @@ export class FASTAnchor extends FASTElement {
* Prompts the user to save the linked URL. See {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a | <a> element } for more information.
* @public
* @remarks
* HTML Attribute: download
* HTML Attribute: `download`
*/
@attr
public download: string;

Просмотреть файл

@ -39,8 +39,10 @@ const storyTemplate = html<StoryArgs<FASTAnchor>>`
</fast-anchor>
`;
export default {
const meta: Meta<FASTAnchor> = {
title: "Anchor",
component: "fast-anchor",
render: renderComponent(storyTemplate).bind({}),
argTypes: {
download: { control: "text" },
href: { control: "text" },
@ -72,30 +74,32 @@ export default {
ariaRoledescription: { control: "text" },
storyContent: { table: { disable: true } },
},
} as Meta<FASTAnchor>;
};
export default meta;
export const Anchor: Story<FASTAnchor> = renderComponent(storyTemplate).bind({});
Anchor.args = {
href: "https://www.fast.design/",
storyContent: "Anchor",
export const Anchor: Story<FASTAnchor> = {
args: {
href: "https://www.fast.design/",
storyContent: "Anchor",
},
};
export const AnchorWithSlottedStartEnd: Story<FASTAnchor> = renderComponent(
storyTemplate
).bind({});
AnchorWithSlottedStartEnd.args = {
href: "https://www.fast.design/",
storyContent: html`
<svg slot="start" width="20" height="20"><use href="#test-icon" /></svg>
Anchor
<svg slot="end" width="20" height="20"><use href="#test-icon-2" /></svg>
`,
export const AnchorWithSlottedStartEnd: Story<FASTAnchor> = {
args: {
href: "https://www.fast.design/",
storyContent: html`
<svg slot="start" width="20" height="20"><use href="#test-icon" /></svg>
Anchor
<svg slot="end" width="20" height="20"><use href="#test-icon-2" /></svg>
`,
},
};
export const AnchorWithSlottedIconContent: Story<FASTAnchor> = Anchor.bind({});
AnchorWithSlottedIconContent.args = {
href: "https://www.fast.design/",
storyContent: html`
<svg width="20" height="20"><use href="#test-icon" /></svg>
`,
export const AnchorWithSlottedIconContent: Story = {
args: {
href: "https://www.fast.design/",
storyContent: html`
<svg width="20" height="20"><use href="#test-icon" /></svg>
`,
},
};

Просмотреть файл

@ -1,29 +1,15 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTAnchoredRegion } from "./anchored-region.js";
test.describe("Anchored Region", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should set positioning modes to 'uncontrolled' by default", async ({
page,
}) => {
const element = page.locator("fast-anchored-region");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-anchored-region");
root = page.locator("#root");
await page.goto(fixtureURL("anchored-region--anchored-region"));
});
test.afterAll(async () => {
await page.close();
});
test("should set positioning modes to 'uncontrolled' by default", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-anchored-region></fast-anchored-region>
`;
@ -37,10 +23,13 @@ test.describe("Anchored Region", () => {
);
});
test("should assign anchor and viewport elements by id", async () => {
test("should assign anchor and viewport elements by id", async ({ page }) => {
const element = page.locator("fast-anchored-region");
const anchorId = "anchor";
await root.evaluate(
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { anchorId }) => {
node.innerHTML = /* html */ `
<div id="${anchorId}"></div>
@ -61,8 +50,13 @@ test.describe("Anchored Region", () => {
expect(anchorElementId).toBe(anchorId);
});
test("should be sized to match content by default", async () => {
await root.evaluate(node => {
test("should be sized to match content by default", async ({ page }) => {
const element = page.locator("fast-anchored-region");
const content = element.locator("#content");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-anchored-region>
<div id="content" style="width: 100px; height: 100px;"></div>
@ -72,8 +66,6 @@ test.describe("Anchored Region", () => {
const elementClientHeight = await element.evaluate(node => node.clientHeight);
const content = element.locator("#content");
const contentClientHeight = await content.evaluate(node => node.clientHeight);
expect(elementClientHeight).toBe(contentClientHeight);

Просмотреть файл

@ -1,5 +1,4 @@
export {
AnchoredRegionConfig,
FlyoutPosBottom,
FlyoutPosBottomFill,
FlyoutPosTallest,
@ -7,14 +6,14 @@ export {
FlyoutPosTop,
FlyoutPosTopFill,
} from "./anchored-region-config.js";
export type { AnchoredRegionConfig } from "./anchored-region-config.js";
export { FASTAnchoredRegion } from "./anchored-region.js";
export {
AnchoredRegionPositionLabel,
AutoUpdateMode,
AxisPositioningMode,
AxisScalingMode,
Dimension,
HorizontalPosition,
VerticalPosition,
} from "./anchored-region.options.js";
export type { AxisPositioningMode, Dimension } from "./anchored-region.options.js";
export { anchoredRegionTemplate } from "./anchored-region.template.js";

Просмотреть файл

@ -13,7 +13,7 @@ import {
const storyTemplate = html<StoryArgs<FASTAnchoredRegion>>`
<div style="min-height: 100px">
<fast-button class="anchor">Anchor</fast-button>
<div class="anchor">Anchor</div>
<fast-anchored-region
class="region"
?horizontal-inset="${x => x.horizontalInset}"
@ -38,7 +38,7 @@ const storyTemplate = html<StoryArgs<FASTAnchoredRegion>>`
</div>
`;
export default {
const meta: Meta<typeof FASTAnchoredRegion> = {
title: "Anchored Region",
args: {
storyContent: html`
@ -57,33 +57,33 @@ export default {
anchorId: { table: { disable: true } },
autoUpdateMode: {
control: "select",
options: Object.values(AutoUpdateMode),
options: AutoUpdateMode,
},
fixedPlacement: { control: "boolean" },
horizontalDefaultPosition: {
control: "select",
options: Object.values(HorizontalPosition),
options: HorizontalPosition,
},
horizontalInset: { control: "boolean" },
horizontalPositioningMode: {
control: "select",
options: Object.values(AxisPositioningMode),
options: AxisPositioningMode,
},
horizontalScaling: {
control: "select",
options: Object.values(AxisScalingMode),
options: AxisScalingMode,
},
horizontalThreshold: { control: "number" },
horizontalViewportLock: { control: "boolean" },
storyContent: { table: { disable: true } },
verticalDefaultPosition: {
control: "select",
options: Object.values(VerticalPosition),
options: VerticalPosition,
},
verticalInset: { control: "boolean" },
verticalPositioningMode: {
control: "select",
options: Object.values(AxisPositioningMode),
options: AxisPositioningMode,
},
verticalScaling: {
control: "select",
@ -93,22 +93,25 @@ export default {
verticalViewportLock: { control: "boolean" },
viewport: { control: "text" },
},
decorators: [
(Story, { args }) => {
// IDs are generated to ensure that they're unique for the docs page
const renderedStory = Story() as DocumentFragment;
const anchor = renderedStory.querySelector(".anchor") as HTMLElement;
const region = renderedStory.querySelector(".region") as HTMLElement;
};
const anchorId = args.anchorId ?? uniqueId("anchor");
meta.decorators = [
(Story, { args }) => {
// IDs are generated to ensure that they're unique for the docs page
const renderedStory = Story() as DocumentFragment;
const anchor = renderedStory.querySelector(".anchor") as FASTAnchoredRegion;
const region = renderedStory.querySelector(".region") as HTMLElement;
anchor.id = anchorId;
region.id = uniqueId("region");
region.setAttribute("anchor", anchorId);
return renderedStory;
},
],
} as Meta<FASTAnchoredRegion>;
const anchorId = args.anchorId ?? uniqueId("anchor");
anchor.id = anchorId;
region.id = uniqueId("region");
region.setAttribute("anchor", anchorId);
return renderedStory;
},
];
export default meta;
export const AnchoredRegion: Story<FASTAnchoredRegion> = renderComponent(
storyTemplate

Просмотреть файл

@ -1,33 +1,14 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTBreadcrumbItem } from "./breadcrumb-item.js";
test.describe("Breadcrumb item", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
test("should include a `role` of `listitem`", async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-breadcrumb-item");
element = page.locator("fast-breadcrumb-item");
root = page.locator("#root");
control = element.locator(".control");
await page.goto(fixtureURL("breadcrumb-item--breadcrumb-item"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a `role` of `listitem`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb-item></fast-breadcrumb-item>
`;
@ -36,8 +17,14 @@ test.describe("Breadcrumb item", () => {
await expect(element.locator("> div")).toHaveAttribute("role", "listitem");
});
test("should render an internal anchor when the `href` attribute is not provided", async () => {
await root.evaluate(node => {
test("should render an internal anchor when the `href` attribute is not provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-breadcrumb-item");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb-item></fast-breadcrumb-item>
`;
@ -54,8 +41,14 @@ test.describe("Breadcrumb item", () => {
await expect(element.locator("a")).toHaveCount(1);
});
test("should render an internal anchor when the `href` attribute is provided", async () => {
await root.evaluate(node => {
test("should render an internal anchor when the `href` attribute is provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-breadcrumb-item");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb-item href="https://fast.design"></fast-breadcrumb-item>
`;
@ -72,8 +65,14 @@ test.describe("Breadcrumb item", () => {
});
});
test("should add an element with a class of `separator` when the `separator` property is true", async () => {
await root.evaluate(node => {
test("should add an element with a class of `separator` when the `separator` property is true", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-breadcrumb-item");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb-item></fast-breadcrumb-item>
`;
@ -117,19 +116,27 @@ test.describe("Breadcrumb item", () => {
};
for (const [attribute, value] of Object.entries(attributes)) {
const attrToken = spinalCase(attribute);
const attributeSpinalCase = spinalCase(attribute);
test(`should set the \`${attrToken}\` attribute to \`${value}\` on the internal anchor`, async () => {
await root.evaluate(
(node, { attrToken, value }) => {
node.innerHTML = /* html */ `
<fast-breadcrumb-item ${attrToken}="${value}" href="#"></fast-breadcrumb-item>
`;
test(`should set the \`${attributeSpinalCase}\` attribute to \`${value}\` on the internal anchor`, async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-breadcrumb-item");
const control = element.locator(".control");
await page.locator("#root").evaluate(
(root, [attribute, value]) => {
const breadcrumbItem = document.createElement("fast-breadcrumb-item");
breadcrumbItem[attribute] = value;
root.append(breadcrumbItem);
},
{ attrToken, value }
[attribute, value]
);
await expect(control).toHaveAttribute(attrToken, `${value}`);
await expect(control).toHaveAttribute(attributeSpinalCase, `${value}`);
});
}
});

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTBreadcrumb } from "./breadcrumb.js";
test.describe("Breadcrumb", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should have a role of 'navigation'", async ({ page }) => {
const element = page.locator("fast-breadcrumb");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-breadcrumb");
root = page.locator("#root");
await page.goto(fixtureURL("breadcrumb--breadcrumb"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of 'navigation'", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb></fast-breadcrumb>
`;
@ -32,8 +16,14 @@ test.describe("Breadcrumb", () => {
await expect(element).toHaveAttribute("role", "navigation");
});
test("should include an internal element with a `role` of `list`", async () => {
await root.evaluate(node => {
test("should include an internal element with a `role` of `list`", async ({
page,
}) => {
const element = page.locator("fast-breadcrumb");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb></fast-breadcrumb>
`;
@ -42,8 +32,13 @@ test.describe("Breadcrumb", () => {
await expect(element.locator(".list")).toHaveAttribute("role", "list");
});
test("should not render a separator on last item", async () => {
await root.evaluate(node => {
test("should not render a separator on last item", async ({ page }) => {
const element = page.locator("fast-breadcrumb");
const items = element.locator("fast-breadcrumb-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb>
<fast-breadcrumb-item>Item 1</fast-breadcrumb-item>
@ -53,15 +48,19 @@ test.describe("Breadcrumb", () => {
`;
});
const items = element.locator("fast-breadcrumb-item");
await expect(items).toHaveCount(3);
await expect(items.last()).toHaveJSProperty("separator", false);
});
test("should set `aria-current` on the internal anchor of the last node when `href` is present", async () => {
await root.evaluate(node => {
test("should set `aria-current` on the internal anchor of the last node when `href` is present", async ({
page,
}) => {
const element = page.locator("fast-breadcrumb");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb>
<fast-breadcrumb-item>Item 1</fast-breadcrumb-item>
@ -76,8 +75,14 @@ test.describe("Breadcrumb", () => {
).toHaveAttribute("aria-current", "page");
});
test("should remove `aria-current` from any prior breadcrumb item children with child anchors when a new node is appended", async () => {
await root.evaluate(node => {
test("should remove `aria-current` from any prior breadcrumb item children with child anchors when a new node is appended", async ({
page,
}) => {
const element = page.locator("fast-breadcrumb");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-breadcrumb>
<fast-breadcrumb-item>Item 1</fast-breadcrumb-item>

Просмотреть файл

@ -1,33 +1,18 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTButton } from "./button.js";
test.describe("Button", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
test("should set the `disabled` attribute on the internal control", async ({
page,
}) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-button");
element = page.locator("fast-button");
const control = element.locator(".control");
root = page.locator("#root");
control = element.locator(".control");
await page.goto(fixtureURL("button--button"));
});
test.afterAll(async () => {
await page.close();
});
test("should set the `disabled` attribute on the internal control", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-button disabled></fast-button>
`;
@ -42,8 +27,16 @@ test.describe("Button", () => {
await expect(control).not.toHaveBooleanAttribute("disabled");
});
test("should set the `formnovalidate` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `formnovalidate` attribute on the internal control", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
const control = element.locator(".control");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-button formnovalidate></fast-button>
`;
@ -94,8 +87,16 @@ test.describe("Button", () => {
for (const [attribute, value] of Object.entries(attributes)) {
const attrToken = spinalCase(attribute);
test(`should set the \`${attrToken}\` attribute to \`${value}\``, async () => {
await root.evaluate(
test(`should set the \`${attrToken}\` attribute to \`${value}\``, async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
const control = element.locator(".control");
await page.locator("#root").evaluate(
(node, { attrToken, value }) => {
node.innerHTML = /* html */ `
<fast-button ${attrToken}="${value}"></fast-button>
@ -109,8 +110,16 @@ test.describe("Button", () => {
}
});
test("should set the `form` attribute on the internal button when `formId` is provided", async () => {
await root.evaluate(node => {
test("should set the `form` attribute on the internal button when `formId` is provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
const control = element.locator(".control");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-button></fast-button>
`;
@ -123,8 +132,16 @@ test.describe("Button", () => {
await expect(control).toHaveAttribute("form", "foo");
});
test("should set the `autofocus` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `autofocus` attribute on the internal control", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
const control = element.locator(".control");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-button autofocus></fast-button>
`;
@ -139,8 +156,14 @@ test.describe("Button", () => {
await expect(control).not.toHaveBooleanAttribute("autofocus");
});
test("of type `submit` should submit the parent form when clicked", async () => {
await root.evaluate(node => {
test("of type `submit` should submit the parent form when clicked", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-button type="submit">Submit Button</fast-button>
@ -167,8 +190,14 @@ test.describe("Button", () => {
expect(wasSubmitted).toBeTruthy();
});
test("of type `reset` should reset the parent form when clicked", async () => {
await root.evaluate(node => {
test("of type `reset` should reset the parent form when clicked", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<input type="text" value="foo" />
@ -194,8 +223,12 @@ test.describe("Button", () => {
expect(wasReset).toBeTruthy();
});
test("should not propagate when clicked and `disabled` is true", async () => {
await root.evaluate(node => {
test("should not propagate when clicked and `disabled` is true", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-button");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-button disabled>Disabled Button</fast-button>
`;

Просмотреть файл

@ -1,30 +1,10 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTCalendar } from "./calendar.js";
import { DateFormatter } from "./date-formatter.js";
test.describe("Calendar", () => {
let page: Page;
let element: Locator;
let root: Locator;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
element = page.locator("fast-calendar");
root = page.locator("#root");
await page.goto(fixtureURL("calendar--calendar"));
});
test.afterAll(async () => {
await page.close();
});
test.describe("DateFormatter", () => {
test("should be able to set properties on construction", async () => {
test("should be able to set properties on construction", () => {
const locale = "en-US";
const dayFormat = "2-digit";
const monthFormat = "narrow";
@ -50,14 +30,14 @@ test.describe("Calendar", () => {
expect(formatter.date.getFullYear()).toBe(2021);
});
test("should return a date string for today by default", async () => {
test("should return a date string for today by default", () => {
const formatter = new DateFormatter();
const today = new Date();
expect(formatter.getDate()).toBe(formatter.getDate(today));
});
test("should be able to get a date string for a specific date", async () => {
test("should be able to get a date string for a specific date", () => {
const formatter = new DateFormatter();
const day = 2;
const month = 1;
@ -74,13 +54,13 @@ test.describe("Calendar", () => {
).toBe(dateString);
});
test("should default formatting to [weekday='long'], [month='long'] [day='numeric'], [year='numeric'] string", async () => {
test("should default formatting to [weekday='long'], [month='long'] [day='numeric'], [year='numeric'] string", () => {
const formatter = new DateFormatter({ date: "1-1-2020" });
expect(formatter.getDate()).toBe("Wednesday, January 1, 2020");
});
test("should be able to change formats", async () => {
test("should be able to change formats", () => {
const formatter = new DateFormatter({
weekdayFormat: undefined,
monthFormat: "short",
@ -101,21 +81,21 @@ test.describe("Calendar", () => {
).toBe("J 01, 20");
});
test("should return todays day by default for getDay()", async () => {
test("should return todays day by default for getDay()", () => {
const formatter = new DateFormatter();
const today = new Date();
expect(formatter.getDay()).toBe(today.getDate().toString());
});
test("should return formatted days with getDay()", async () => {
test("should return formatted days with getDay()", () => {
const formatter = new DateFormatter();
expect(formatter.getDay(14)).toBe("14");
expect(formatter.getDay(8, "2-digit")).toBe("08");
});
test("should return this month by default for getMonth()", async () => {
test("should return this month by default for getMonth()", () => {
const formatter = new DateFormatter();
const today = new Date();
const months = [
@ -136,7 +116,7 @@ test.describe("Calendar", () => {
expect(formatter.getMonth()).toBe(months[today.getMonth()]);
});
test("should return formatted month with getMonth()", async () => {
test("should return formatted month with getMonth()", () => {
const formatter = new DateFormatter();
expect(formatter.getMonth(1)).toBe("January");
@ -146,21 +126,21 @@ test.describe("Calendar", () => {
expect(formatter.getMonth(5, "2-digit")).toBe("05");
});
test("should return this year by default for getYear()", async () => {
test("should return this year by default for getYear()", () => {
const formatter = new DateFormatter();
const today = new Date();
expect(formatter.getYear()).toBe(today.getFullYear().toString());
});
test("should return formatted year with getYear()", async () => {
test("should return formatted year with getYear()", () => {
const formatter = new DateFormatter({ yearFormat: "2-digit" });
expect(formatter.getYear(2012)).toBe("12");
expect(formatter.getYear(2015, "numeric")).toBe("2015");
});
test("should return formatted weekday string with getWeekday()", async () => {
test("should return formatted weekday string with getWeekday()", () => {
const formatter = new DateFormatter();
//defaults to sunday
@ -170,7 +150,7 @@ test.describe("Calendar", () => {
expect(formatter.getWeekday(4, "narrow")).toBe("T");
});
test("should return a list of formatted weekday labels with getWeekdays()", async () => {
test("should return a list of formatted weekday labels with getWeekdays()", () => {
const formatter = new DateFormatter();
const weekdays = [
"Sunday",
@ -193,7 +173,7 @@ test.describe("Calendar", () => {
);
});
test("should return a localized string when setting the locale", async () => {
test("should return a localized string when setting the locale", () => {
const formatter = new DateFormatter({ locale: "fr-FR", date: "3-1-2015" });
expect(formatter.getDate()).toBe("dimanche 1 mars 2015");
@ -207,8 +187,12 @@ test.describe("Calendar", () => {
});
test.describe("Defaults", () => {
test("should default to the current month and year", async () => {
await root.evaluate(node => {
test("should default to the current month and year", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar></fast-calendar>
`;
@ -220,8 +204,12 @@ test.describe("Calendar", () => {
await expect(element).toHaveJSProperty("year", today.getFullYear());
});
test("should return 5 weeks of days for August 2021", async () => {
await root.evaluate(node => {
test("should return 5 weeks of days for August 2021", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="8" year="2021"></fast-calendar>
`;
@ -240,8 +228,14 @@ test.describe("Calendar", () => {
await expect(day).toHaveCount(35);
});
test("should return 6 weeks of days for August 2021 when min-weeks is set to 6", async () => {
await root.evaluate(node => {
test("should return 6 weeks of days for August 2021 when min-weeks is set to 6", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="8" year="2021" min-weeks="6"></fast-calendar>
`;
@ -260,8 +254,12 @@ test.describe("Calendar", () => {
await expect(day).toHaveCount(42);
});
test("should highlight the current date", async () => {
await root.evaluate(node => {
test("should highlight the current date", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar></fast-calendar>
`;
@ -276,8 +274,14 @@ test.describe("Calendar", () => {
});
test.describe("Month info", () => {
test("should display 31 days for the month of January in the year 2022", async () => {
await root.evaluate(node => {
test("should display 31 days for the month of January in the year 2022", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2022" readonly></fast-calendar>
`;
@ -292,8 +296,14 @@ test.describe("Calendar", () => {
await expect(days).toHaveCount(31);
});
test("should display 28 days for the month of February in the year 2022", async () => {
await root.evaluate(node => {
test("should display 28 days for the month of February in the year 2022", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="2" year="2022" readonly></fast-calendar>
`;
@ -308,8 +318,14 @@ test.describe("Calendar", () => {
await expect(days).toHaveCount(28);
});
test("should display 29 days for the month of February in the year 2020", async () => {
await root.evaluate(node => {
test("should display 29 days for the month of February in the year 2020", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="2" year="2020" readonly></fast-calendar>
`;
@ -324,8 +340,12 @@ test.describe("Calendar", () => {
await expect(days).toHaveCount(29);
});
test("should start on Friday for January 2021", async () => {
await root.evaluate(node => {
test("should start on Friday for January 2021", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" readonly></fast-calendar>
`;
@ -340,8 +360,12 @@ test.describe("Calendar", () => {
await expect(days.nth(5)).toHaveText("1");
});
test("should start on Monday for February 2021", async () => {
await root.evaluate(node => {
test("should start on Monday for February 2021", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="2" year="2021" readonly></fast-calendar>
`;
@ -357,9 +381,13 @@ test.describe("Calendar", () => {
});
});
test.describe("Labels", async () => {
test("should return January for month 1", async () => {
await root.evaluate(node => {
test.describe("Labels", () => {
test("should return January for month 1", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" readonly></fast-calendar>
`;
@ -372,8 +400,12 @@ test.describe("Calendar", () => {
).toBe("January");
});
test("should return Jan for month 1 and short format", async () => {
await root.evaluate(node => {
test("should return Jan for month 1 and short format", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" month-format="short" readonly></fast-calendar>
`;
@ -386,8 +418,12 @@ test.describe("Calendar", () => {
).toBe("Jan");
});
test("should return Mon for Monday by default", async () => {
await root.evaluate(node => {
test("should return Mon for Monday by default", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" readonly></fast-calendar>
`;
@ -400,8 +436,12 @@ test.describe("Calendar", () => {
).toBe("Mon");
});
test("should return Monday weekday for long format", async () => {
await root.evaluate(node => {
test("should return Monday weekday for long format", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" weekday-format="long" readonly></fast-calendar>
`;
@ -414,8 +454,12 @@ test.describe("Calendar", () => {
).toBe("Monday");
});
test("should return M for Monday for narrow format", async () => {
await root.evaluate(node => {
test("should return M for Monday for narrow format", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" weekday-format="narrow" readonly></fast-calendar>
`;
@ -429,9 +473,13 @@ test.describe("Calendar", () => {
});
});
test.describe("Localization", async () => {
test(`should be "mai" for the month May in French`, async () => {
await root.evaluate(node => {
test.describe("Localization", () => {
test(`should be "mai" for the month May in French`, async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="5" year="2021" locale="fr" readonly></fast-calendar>
`;
@ -444,8 +492,14 @@ test.describe("Calendar", () => {
).toBe("mai");
});
test("should have French weekday labels for the fr-FR market", async () => {
await root.evaluate(node => {
test("should have French weekday labels for the fr-FR market", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" locale="fr-FR" readonly></fast-calendar>
`;
@ -466,8 +520,14 @@ test.describe("Calendar", () => {
await expect(weekdayLabels).toHaveText(frenchWeekdays);
});
test('should set the formatted `year` property to "1942" when the `year` attribute is "2021" for the Hindu calendar', async () => {
await root.evaluate(node => {
test('should set the formatted `year` property to "1942" when the `year` attribute is "2021" for the Hindu calendar', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="6" year="2021" locale="hi-IN-u-ca-indian"></fast-calendar>
`;
@ -480,8 +540,14 @@ test.describe("Calendar", () => {
).toBe("1942 शक");
});
test('should set the formatted `year` property to "2564" when the `year` attribute is "2021" for the Buddhist calendar', async () => {
await root.evaluate(node => {
test('should set the formatted `year` property to "2564" when the `year` attribute is "2021" for the Buddhist calendar', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="6" year="2021" locale="th-TH-u-ca-buddhist"></fast-calendar>
`;
@ -494,8 +560,14 @@ test.describe("Calendar", () => {
).toBe("พ.ศ. 2564");
});
test("should not be RTL for languages that are not Arabic or Hebrew", async () => {
await root.evaluate(node => {
test("should not be RTL for languages that are not Arabic or Hebrew", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="1" year="2021" locale="fr-FR" readonly></fast-calendar>
`;
@ -514,9 +586,12 @@ test.describe("Calendar", () => {
}
});
/* FIXME this test is an incorrect assertion */
test.skip("Should be RTL for Arabic language", async () => {
await root.evaluate(node => {
test("Should be RTL for Arabic language", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="8" year="2021" locale="ar-XE-u-ca-islamic-nu-arab" readonly></fast-calendar>
`;
@ -528,13 +603,19 @@ test.describe("Calendar", () => {
nodes.map(node => node.innerHTML.trim())
);
expect(parseInt(dateStrings[0]) < parseInt(dateStrings[1])).toBeFalsy();
expect(
parseInt(dateStrings[0], 10) < parseInt(dateStrings[1], 10)
).toBeFalsy();
});
});
test.describe("Day states", async () => {
test("should not show date as disabled by default", async () => {
await root.evaluate(node => {
test.describe("Day states", () => {
test("should not show date as disabled by default", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="5" year="2021" readonly></fast-calendar>
`;
@ -559,8 +640,14 @@ test.describe("Calendar", () => {
await expect(disabled).toHaveCount(0);
});
test("should show date as disabled when added to disabled-dates attribute", async () => {
await root.evaluate(node => {
test("should show date as disabled when added to disabled-dates attribute", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="5" year="2021" disabled-dates="5-6-2021,5-7-2021,5-8-2021" readonly></fast-calendar>
`;
@ -588,8 +675,12 @@ test.describe("Calendar", () => {
await expect(disabled).toHaveCount(3);
});
test("should not show date as selected by default", async () => {
await root.evaluate(node => {
test("should not show date as selected by default", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="5" year="2021" readonly></fast-calendar>
`;
@ -602,21 +693,26 @@ test.describe("Calendar", () => {
).toBe(false);
expect(
element.evaluate(
(node: FASTCalendar) =>
node
.getDayClassNames({ month: 5, day: 7, year: 2021 })
.indexOf("selected") === -1
await element.evaluate((node: FASTCalendar) =>
node
.getDayClassNames({ month: 5, day: 7, year: 2021 })
.indexOf("selected")
)
).toBeTruthy();
).toBe(-1);
const selected = element.locator(".selected");
await expect(selected).toHaveCount(0);
});
test("should show date as selected when added to selected-dates attribute", async () => {
await root.evaluate(node => {
test("should show date as selected when added to selected-dates attribute", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-calendar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-calendar month="5" year="2021" selected-dates="5-6-2021,5-7-2021,5-8-2021" readonly></fast-calendar>
`;
@ -629,18 +725,17 @@ test.describe("Calendar", () => {
).toBe(true);
expect(
await element.evaluate(
(node: FASTCalendar) =>
node
.getDayClassNames({
month: 5,
day: 7,
year: 2021,
selected: true,
})
.indexOf("selected") >= 0
await element.evaluate((node: FASTCalendar) =>
node
.getDayClassNames({
month: 5,
day: 7,
year: 2021,
selected: true,
})
.indexOf("selected")
)
).toBeTruthy();
).toBeGreaterThanOrEqual(0);
const selected = element.locator(".selected");

Просмотреть файл

@ -1,9 +1,10 @@
import { attr, FASTElement, nullableNumberConverter } from "@microsoft/fast-element";
import { keyEnter } from "@microsoft/fast-web-utilities";
import type { StartEndOptions } from "../patterns/start-end.js";
import { StartEnd } from "../patterns/start-end.js";
import type { TemplateElementDependency } from "../patterns/tag-for.js";
import { applyMixins } from "../utilities/apply-mixins.js";
import type { StaticallyComposableHTML } from "../utilities/template-helpers.js";
import { StartEnd, TemplateElementDependency } from "../patterns/index.js";
import type { StartEndOptions } from "../patterns/start-end.js";
import { DayFormat, MonthFormat, WeekdayFormat, YearFormat } from "./calendar.options.js";
import { DateFormatter } from "./date-formatter.js";

Просмотреть файл

@ -1,32 +1,13 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTCheckbox } from "./checkbox.js";
test.describe("Checkbox", () => {
let page: Page;
let element: Locator;
let root: Locator;
let form: Locator;
test("should have a role of `checkbox`", async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-checkbox");
element = page.locator("fast-checkbox");
root = page.locator("#root");
form = page.locator("form");
await page.goto(fixtureURL("checkbox--checkbox"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `checkbox`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -34,8 +15,12 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("role", "checkbox");
});
test("should set a tabindex of 0 on the element", async () => {
await root.evaluate(node => {
test("should set a tabindex of 0 on the element", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -46,8 +31,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should set a default `aria-checked` value when `checked` is not defined", async () => {
await root.evaluate(node => {
test("should set a default `aria-checked` value when `checked` is not defined", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -58,8 +49,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should set the `aria-checked` attribute equal to the `checked` property", async () => {
await root.evaluate(node => {
test("should set the `aria-checked` attribute equal to the `checked` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox checked></fast-checkbox>
`;
@ -74,8 +71,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should NOT set a default `aria-required` value when `required` is not defined", async () => {
await root.evaluate(node => {
test("should NOT set a default `aria-required` value when `required` is not defined", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -86,8 +89,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-required", "false");
});
test("should set the `aria-required` attribute equal to the `required` property", async () => {
await root.evaluate(node => {
test("should set the `aria-required` attribute equal to the `required` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -106,8 +115,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-required", "false");
});
test("should set a default `aria-disabled` value when `disabled` is not defined", async () => {
await root.evaluate(node => {
test("should set a default `aria-disabled` value when `disabled` is not defined", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -118,8 +133,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set the `aria-disabled` attribute equal to the `disabled` property", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to the `disabled` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -138,8 +159,12 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should NOT set a tabindex when `disabled` is true", async () => {
await root.evaluate(node => {
test("should NOT set a tabindex when `disabled` is true", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox disabled></fast-checkbox>
`;
@ -150,8 +175,14 @@ test.describe("Checkbox", () => {
await expect(element).not.toHaveAttribute("tabindex", "0");
});
test("should set the aria-checked value to 'mixed' when indeterminate property is true", async () => {
await root.evaluate(node => {
test("should set the aria-checked value to 'mixed' when indeterminate property is true", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -170,8 +201,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should set off `indeterminate` on `checked` change by user click", async () => {
await root.evaluate(node => {
test("should set off `indeterminate` on `checked` change by user click", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox>checkbox</fast-checkbox>
`;
@ -188,8 +225,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveJSProperty("indeterminate", false);
});
test("should set off `indeterminate` on `checked` change by user keypress", async () => {
await root.evaluate(node => {
test("should set off `indeterminate` on `checked` change by user keypress", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -206,8 +249,14 @@ test.describe("Checkbox", () => {
await expect(element).toHaveJSProperty("indeterminate", false);
});
test("should add a class of `label` to the internal label when default slotted content exists", async () => {
await root.evaluate(node => {
test("should add a class of `label` to the internal label when default slotted content exists", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox>
<span>Label</span>
@ -220,8 +269,14 @@ test.describe("Checkbox", () => {
await expect(label).toHaveClass(/label/);
});
test("should add classes of `label` and `label__hidden` to the internal label when default slotted content exists", async () => {
await root.evaluate(node => {
test("should add classes of `label` and `label__hidden` to the internal label when default slotted content exists", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox>
<span>Label</span>
@ -240,8 +295,14 @@ test.describe("Checkbox", () => {
await expect(label).toHaveClass(/label label__hidden/);
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -255,29 +316,37 @@ test.describe("Checkbox", () => {
await expect(element).toHaveJSProperty("value", initialValue);
});
test("should initialize to the provided `value` attribute when set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` attribute when set pre-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox value="foo"></fast-checkbox>
`;
});
const element = page.locator("fast-checkbox");
const value = await element.evaluate((node: FASTCheckbox) => node.value);
expect(value).toBe("foo");
});
test("should initialize to the provided `value` attribute when set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` attribute when set post-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
});
const element = page.locator("fast-checkbox");
const expectedValue = "foobar";
await element.evaluate((node: FASTCheckbox, expectedValue) => {
@ -287,8 +356,12 @@ test.describe("Checkbox", () => {
await expect(element).toHaveJSProperty("value", expectedValue);
});
test("should initialize to the provided `value` property when set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` property when set pre-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-checkbox></fast-checkbox>
`;
@ -307,8 +380,12 @@ test.describe("Checkbox", () => {
expect(value).toBe(expectedValue);
});
test("should be invalid when unchecked", async () => {
await root.evaluate(node => {
test("should be invalid when unchecked", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-checkbox required></fast-checkbox>
@ -321,8 +398,12 @@ test.describe("Checkbox", () => {
).toBe(true);
});
test("should be valid when checked", async () => {
await root.evaluate(node => {
test("should be valid when checked", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-checkbox required>checkbox</fast-checkbox>
@ -339,8 +420,16 @@ test.describe("Checkbox", () => {
).toBe(false);
});
test("should set the `checked` property to false if the `checked` attribute is unset", async () => {
await root.evaluate(node => {
test("should set the `checked` property to false if the `checked` attribute is unset", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
const form = page.locator("form");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-checkbox></fast-checkbox>
@ -363,8 +452,16 @@ test.describe("Checkbox", () => {
await expect(element).toHaveJSProperty("checked", false);
});
test("should set its checked property to true if the checked attribute is set", async () => {
await root.evaluate(node => {
test("should set its checked property to true if the checked attribute is set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
const form = page.locator("form");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-checkbox></fast-checkbox>
@ -387,8 +484,16 @@ test.describe("Checkbox", () => {
await expect(element).toHaveJSProperty("checked", true);
});
test("should put the control into a clean state, where checked attribute modifications change the checked property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where checked attribute modifications change the checked property prior to user or programmatic interaction", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-checkbox");
const form = page.locator("form");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-checkbox required></fast-checkbox>

Просмотреть файл

@ -1,5 +1,5 @@
import { html } from "@microsoft/fast-element";
import { css } from "@microsoft/fast-element";
import { css, html } from "@microsoft/fast-element";
import checkedIndicator from "../../../statics/svg/checked-indicator.svg";
import { FASTCheckbox } from "../checkbox.js";
import { checkboxTemplate } from "../checkbox.template.js";
@ -133,21 +133,10 @@ const styles = css`
FASTCheckbox.define({
name: "fast-checkbox",
template: checkboxTemplate({
checkedIndicator: /* html */ html`
<svg
part="checked-indicator"
class="checked-indicator"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.143 12.6697L15.235 4.5L16.8 5.90363L8.23812 15.7667L3.80005 11.2556L5.27591 9.7555L8.143 12.6697Z"
/>
</svg>
checkedIndicator: html`
<div part="checked-indicator">${html.partial(checkedIndicator)}</div>
`,
indeterminateIndicator: /* html */ html`
indeterminateIndicator: html`
<div part="indeterminate-indicator" class="indeterminate-indicator"></div>
`,
}),

Просмотреть файл

@ -1,32 +1,17 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTCombobox } from "./combobox.js";
test.describe("Combobox", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
test('should include a control with a `role` attribute equal to "combobox"', async ({
page,
}) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-combobox");
element = page.locator("fast-combobox");
const control = element.locator(`input[role="combobox"]`);
root = page.locator("#root");
control = element.locator(`input[role="combobox"]`);
await page.goto(fixtureURL("combobox--combobox"));
});
test.afterAll(async () => {
await page.close();
});
test('should include a control with a `role` attribute equal to "combobox"', async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
@ -39,8 +24,14 @@ test.describe("Combobox", () => {
await expect(control).toHaveCount(1);
});
test("should set the `aria-disabled` attribute equal to the `disabled` property", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to the `disabled` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
@ -65,8 +56,14 @@ test.describe("Combobox", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set and remove the `tabindex` attribute based on the value of the `disabled` property", async () => {
await root.evaluate(node => {
test("should set and remove the `tabindex` attribute based on the value of the `disabled` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox disabled>
<fast-option>Option 1</fast-option>
@ -85,8 +82,16 @@ test.describe("Combobox", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should NOT set the `value` property to the first available option", async () => {
await root.evaluate(node => {
test("should NOT set the `value` property to the first available option", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
const control = element.locator(`input[role="combobox"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
@ -101,8 +106,16 @@ test.describe("Combobox", () => {
await expect(control).toHaveValue("");
});
test("should set the `placeholder` attribute on the internal control equal to the `placeholder` attribute", async () => {
await root.evaluate(node => {
test("should set the `placeholder` attribute on the internal control equal to the `placeholder` attribute", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
const control = element.locator(`input[role="combobox"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox placeholder="placeholder text">
<fast-option>Option 1</fast-option>
@ -117,8 +130,16 @@ test.describe("Combobox", () => {
await expect(control).toHaveAttribute("placeholder", "placeholder text");
});
test("should set the control's `aria-controls` attribute to the ID of the internal listbox element while open", async () => {
await root.evaluate(node => {
test("should set the control's `aria-controls` attribute to the ID of the internal listbox element while open", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
const control = element.locator(`input[role="combobox"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
@ -147,8 +168,16 @@ test.describe("Combobox", () => {
await expect(control).toHaveAttribute("aria-controls", "");
});
test("should set the control's `aria-activedescendant` property to the ID of the currently selected option while open", async () => {
await root.evaluate(node => {
test("should set the control's `aria-activedescendant` property to the ID of the currently selected option while open", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
const control = element.locator(`input[role="combobox"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
@ -166,7 +195,7 @@ test.describe("Combobox", () => {
node.open = true;
});
await expect(element).toHaveAttribute("aria-activedescendant", "");
await expect(element).not.toHaveAttribute("aria-activedescendant");
const optionsCount = await options.count();
@ -188,11 +217,17 @@ test.describe("Combobox", () => {
await expect(control).hasAttribute("aria-activedescendant");
await expect(element).toHaveAttribute("aria-activedescendant", "");
await expect(element).not.toHaveAttribute("aria-activedescendant");
});
test("should set its value to the first option with the `selected` attribute present", async () => {
await root.evaluate(node => {
test("should set its value to the first option with the `selected` attribute present", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const combobox = document.createElement("fast-combobox");
@ -216,8 +251,14 @@ test.describe("Combobox", () => {
await expect(element).toHaveJSProperty("value", "two");
});
test("should return the same value when the `value` property is set before connecting", async () => {
await root.evaluate(node => {
test("should return the same value when the `value` property is set before connecting", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const combobox = document.createElement("fast-combobox") as FASTCombobox;
@ -228,8 +269,14 @@ test.describe("Combobox", () => {
expect(await element.evaluate((node: FASTCombobox) => node.value)).toBe("test");
});
test("should return the same value when the `value` property is set after connecting", async () => {
await root.evaluate(node => {
test("should return the same value when the `value` property is set after connecting", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const combobox = document.createElement("fast-combobox") as FASTCombobox;
@ -243,8 +290,14 @@ test.describe("Combobox", () => {
await expect(element).toHaveJSProperty("value", "test");
});
test("should display the listbox when the `open` property is true before connecting", async () => {
await root.evaluate(node => {
test("should display the listbox when the `open` property is true before connecting", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
@ -267,121 +320,137 @@ test.describe("Combobox", () => {
await expect(listbox).toBeVisible();
});
test.describe(
"should NOT emit a 'change' event when the value changes by user input while open",
() => {
test("via arrow down key", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
test.describe("should NOT emit a 'change' event when the value changes by user input while open", () => {
test("via arrow down key", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-combobox>
`;
});
await element.click();
await element.locator(".listbox").waitFor({ state: "visible" });
await expect(element).toHaveBooleanAttribute("open");
const [wasChanged] = await Promise.all([
element.evaluate(node =>
Promise.race<boolean>([
new Promise(resolve => {
node.addEventListener("change", () => resolve(true));
}),
new Promise(resolve => setTimeout(() => resolve(false), 100)),
])
),
element.press("ArrowDown"),
]);
expect(wasChanged).toBeFalsy();
});
test("via arrow up key", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
await element.click();
await element.locator(".listbox").waitFor({ state: "visible" });
await expect(element).toHaveBooleanAttribute("open");
const [wasChanged] = await Promise.all([
element.evaluate(node =>
Promise.race<boolean>([
new Promise(resolve => {
node.addEventListener("change", () => resolve(true));
}),
new Promise(resolve =>
requestAnimationFrame(() => setTimeout(() => resolve(false)))
),
])
),
element.press("ArrowDown"),
]);
expect(wasChanged).toBeFalsy();
});
test("via arrow up key", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-combobox>
`;
});
await element.evaluate((node: FASTCombobox) => {
node.value = "two";
});
await element.click();
await element.locator(".listbox").waitFor({ state: "visible" });
expect(
await element.evaluate(node => node.hasAttribute("open"))
).toBeTruthy();
const [wasChanged] = await Promise.all([
element.evaluate(node =>
Promise.race<boolean>([
new Promise(resolve => {
node.addEventListener("change", () => resolve(true));
}),
new Promise(resolve => setTimeout(() => resolve(false), 100)),
])
),
element.press("ArrowUp"),
]);
expect(wasChanged).toBeFalsy();
});
}
);
test.describe(
"should NOT emit a 'change' event when the value changes by programmatic interaction",
() => {
test("via end key", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
await element.evaluate((node: FASTCombobox) => {
node.value = "two";
});
await element.click();
await element.locator(".listbox").waitFor({ state: "visible" });
expect(
await element.evaluate(node => node.hasAttribute("open"))
).toBeTruthy();
const [wasChanged] = await Promise.all([
element.evaluate(node =>
Promise.race<boolean>([
new Promise(resolve => {
node.addEventListener("change", () => resolve(true));
}),
new Promise(resolve =>
requestAnimationFrame(() => setTimeout(() => resolve(false)))
),
])
),
element.press("ArrowUp"),
]);
expect(wasChanged).toBeFalsy();
});
});
test.describe("should NOT emit a 'change' event when the value changes by programmatic interaction", () => {
test("via end key", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-combobox>
`;
});
await element.evaluate((node: FASTCombobox) => {
node.value = "two";
});
const [wasChanged] = await Promise.all([
element.evaluate(node =>
Promise.race<boolean>([
new Promise(resolve => {
node.addEventListener("change", () => resolve(true));
}),
new Promise(resolve => setTimeout(() => resolve(false), 50)),
])
),
element.press("End"),
]);
expect(wasChanged).toBeFalsy();
await expect(element).toHaveJSProperty("value", "two");
});
}
);
await element.evaluate((node: FASTCombobox) => {
node.value = "two";
});
const [wasChanged] = await Promise.all([
element.evaluate(node =>
Promise.race<boolean>([
new Promise(resolve => {
node.addEventListener("change", () => resolve(true));
}),
new Promise(resolve =>
requestAnimationFrame(() => setTimeout(() => resolve(false)))
),
])
),
element.press("End"),
]);
expect(wasChanged).toBeFalsy();
await expect(element).toHaveJSProperty("value", "two");
});
});
test.describe("when the owning form's reset() function is invoked", () => {
test("should reset the value property to its initial value", async () => {
await root.evaluate(node => {
test("should reset the value property to its initial value", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-combobox value="one">
@ -408,8 +477,14 @@ test.describe("Combobox", () => {
await expect(element).toHaveJSProperty("value", "one");
});
test("should reset its value property to the first option with the `selected` attribute present", async () => {
await root.evaluate(node => {
test("should reset its value property to the first option with the `selected` attribute present", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-combobox>
@ -439,36 +514,30 @@ test.describe("Combobox", () => {
});
});
test("should focus the control when an associated label is clicked", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-combobox>
`;
});
test("should focus the control when an associated label is clicked", async ({
page,
}) => {
// Skip this test if the browser if ElementInternals isn't supported
if (!(await page.evaluate(() => window.hasOwnProperty("ElementInternals")))) {
test.skip();
}
await page.goto("http://localhost:6006");
const element = page.locator("fast-combobox");
const label = page.locator("label");
await root.evaluate(
(node, { element }) => {
const label = document.createElement("label");
label.setAttribute("for", "test-combobox");
label.textContent = "label";
element.id = "test-combobox";
node.append(label);
},
{ element: await element.evaluateHandle((node: FASTCombobox) => node) }
);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-combobox id="test-combobox">
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-combobox>
<label for="test-combobox">label</label>
`;
});
await label.click();

Просмотреть файл

@ -1,12 +1,19 @@
import { autoUpdate, computePosition, flip, hide, size } from "@floating-ui/dom";
import {
autoUpdate,
computePosition,
ElementRects,
flip,
hide,
size,
} from "@floating-ui/dom";
import { attr, Observable, observable, Updates } from "@microsoft/fast-element";
import { limit, uniqueId } from "@microsoft/fast-web-utilities";
import type { StaticallyComposableHTML } from "../utilities/template-helpers.js";
import type { FASTListboxOption } from "../listbox-option/listbox-option.js";
import { DelegatesARIAListbox } from "../listbox/listbox.js";
import { StartEnd } from "../patterns/start-end.js";
import type { StartEndOptions } from "../patterns/start-end.js";
import { StartEnd } from "../patterns/start-end.js";
import { applyMixins } from "../utilities/apply-mixins.js";
import type { StaticallyComposableHTML } from "../utilities/template-helpers.js";
import { FormAssociatedCombobox } from "./combobox.form-associated.js";
import { ComboboxAutocomplete } from "./combobox.options.js";
@ -596,7 +603,13 @@ export class FASTCombobox extends FormAssociatedCombobox {
middleware: [
flip(),
size({
apply: ({ availableHeight, rects }) => {
apply: ({
availableHeight,
rects,
}: {
availableHeight: number;
rects: ElementRects;
}) => {
Object.assign(this.listbox.style, {
maxHeight: `${availableHeight}px`,
width: `${rects.reference.width}px`,

Просмотреть файл

@ -146,9 +146,9 @@ export const myDataGrid = DataGrid.compose({
### Functions
| Name | Description | Parameters | Return |
| -------------------------------- | ----------- | ------------------------ | --------------------- |
| `defaultCellFocusTargetCallback` | | `cell: FASTDataGridCell` | `HTMLElement or null` |
| Name | Description | Parameters | Return |
| -------------------------------- | ------------------------------------------------------------------- | ------------------------ | ------ |
| `defaultCellFocusTargetCallback` | Basic focusTargetCallback that returns the first child of the cell. | `cell: FASTDataGridCell` | |
<hr/>

Просмотреть файл

@ -1,32 +1,16 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTDataGridCell } from "./data-grid-cell.js";
import { DataGridCellTypes } from "./data-grid.options.js";
declare const FAST: any;
test.describe("Data grid cell", () => {
let page: Page;
let element: Locator;
let root: Locator;
test('should set the `role` attribute to "gridcell" by default', async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-data-grid-cell");
element = page.locator("fast-data-grid-cell");
root = page.locator("#root");
await page.goto(fixtureURL("data-grid-data-grid-cell--data-grid-cell"));
});
test.afterAll(async () => {
await page.close();
});
test('should set the `role` attribute to "gridcell" by default', async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -35,8 +19,12 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveAttribute("role", "gridcell");
});
test("should have a tabIndex of -1 by default", async () => {
await root.evaluate(node => {
test("should have a tabIndex of -1 by default", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -45,8 +33,14 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveAttribute("tabindex", "-1");
});
test('should set the `role` attribute to "columnheader" when the `cell-type` attribute is "columnheader"', async () => {
await root.evaluate(node => {
test('should set the `role` attribute to "columnheader" when the `cell-type` attribute is "columnheader"', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell cell-type="columnheader"></fast-data-grid-cell>
`;
@ -55,8 +49,14 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveAttribute("role", "columnheader");
});
test('should set the `role` attribute to "rowheader" when the `cell-type` attribute is "rowheader"', async () => {
await root.evaluate(node => {
test('should set the `role` attribute to "rowheader" when the `cell-type` attribute is "rowheader"', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell cell-type="rowheader"></fast-data-grid-cell>
`;
@ -65,8 +65,14 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveAttribute("role", "rowheader");
});
test("should set the `grid-column` CSS property to match the `grid-column` attribute", async () => {
await root.evaluate(node => {
test("should set the `grid-column` CSS property to match the `grid-column` attribute", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell grid-column="2"></fast-data-grid-cell>
`;
@ -77,8 +83,12 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveCSS("grid-column-end", "auto");
});
test("should not render data if no columndefinition provided", async () => {
await root.evaluate(node => {
test("should not render data if no columndefinition provided", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -96,8 +106,12 @@ test.describe("Data grid cell", () => {
await expect(element).toBeEmpty();
});
test("should render data when a column definition is provided", async () => {
await root.evaluate(node => {
test("should render data when a column definition is provided", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -116,8 +130,12 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveText("data grid cell value 1");
});
test("should render a custom cell template when provided", async () => {
await root.evaluate(node => {
test("should render a custom cell template when provided", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -133,8 +151,12 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveText("custom cell template");
});
test("should render a custom header cell template if provided", async () => {
await root.evaluate(node => {
test("should render a custom header cell template if provided", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell cell-type="columnheader"></fast-data-grid-cell>
`;
@ -150,8 +172,12 @@ test.describe("Data grid cell", () => {
await expect(element).toHaveText("custom header cell template");
});
test(`should fire a "cell-focused" event when focused`, async () => {
await root.evaluate(node => {
test(`should fire a "cell-focused" event when focused`, async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -169,8 +195,14 @@ test.describe("Data grid cell", () => {
).toBeTruthy();
});
test("should focus on custom cell template when a focus target callback is provided", async () => {
await root.evaluate(node => {
test("should focus on custom cell template when a focus target callback is provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell></fast-data-grid-cell>
`;
@ -192,8 +224,14 @@ test.describe("Data grid cell", () => {
);
});
test("should focus on custom header cell template when a focus target callback is provided", async () => {
await root.evaluate(node => {
test("should focus on custom header cell template when a focus target callback is provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-cell");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-cell cell-type="columnheader"></fast-data-grid-cell>
`;

Просмотреть файл

@ -44,7 +44,14 @@ const defaultHeaderCellContentsTemplate: ViewTemplate<FASTDataGridCell> = html`
</template>
`;
// basic focusTargetCallback that returns the first child of the cell
/**
* Basic focusTargetCallback that returns the first child of the cell.
*
* @param cell - the cell to return the focus target for
* @returns the first focusable element in the cell
*
* @public
*/
export const defaultCellFocusTargetCallback = (
cell: FASTDataGridCell
): HTMLElement | null => {

Просмотреть файл

@ -1,31 +1,15 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTDataGridRow } from "./data-grid-row.js";
test.describe("DataGridRow", () => {
const cellQueryString = "fast-data-grid-cell";
let page: Page;
let element: Locator;
let root: Locator;
test('should set the `role` attribute to "row" by default', async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-data-grid-row");
element = page.locator("fast-data-grid-row");
root = page.locator("#root");
await page.goto(fixtureURL("data-grid-data-grid-row--data-grid-row"));
});
test.afterAll(async () => {
await page.close();
});
test('should set the `role` attribute to "row" by default', async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row></fast-data-grid-row>
`;
@ -34,8 +18,14 @@ test.describe("DataGridRow", () => {
await expect(element).toHaveAttribute("role", "row");
});
test("should set `grid-template-columns` style to match attribute", async () => {
await root.evaluate(node => {
test("should set `grid-template-columns` style to match attribute", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row grid-template-columns="1fr 2fr 3fr"></fast-data-grid-row>
`;
@ -47,8 +37,12 @@ test.describe("DataGridRow", () => {
);
});
test("should fire an event when a child cell is focused", async () => {
await root.evaluate(node => {
test("should fire an event when a child cell is focused", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row>
<fast-data-grid-cell></fast-data-grid-cell>
@ -74,8 +68,12 @@ test.describe("DataGridRow", () => {
expect(wasFocused).toBeTruthy();
});
test("should move focus with left/right arrow key strokes", async () => {
await root.evaluate(node => {
test("should move focus with left/right arrow key strokes", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row>
<fast-data-grid-cell></fast-data-grid-cell>
@ -98,8 +96,14 @@ test.describe("DataGridRow", () => {
await expect(element).toHaveJSProperty("focusColumnIndex", 0);
});
test("should move focus to the start/end of the row with home/end keystrokes", async () => {
await root.evaluate(node => {
test("should move focus to the start/end of the row with home/end keystrokes", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row>
<fast-data-grid-cell></fast-data-grid-cell>
@ -122,10 +126,14 @@ test.describe("DataGridRow", () => {
await expect(element).toHaveJSProperty("focusColumnIndex", 0);
});
test("should render no cells if provided no column definitions", async () => {
test("should render no cells if provided no column definitions", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
const cells = element.locator("fast-data-grid-cell");
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row></fast-data-grid-row>
`;
@ -141,10 +149,16 @@ test.describe("DataGridRow", () => {
await expect(cells).toHaveCount(0);
});
test("should render as many column header cells as specified in column definitions", async () => {
test("should render as many column header cells as specified in column definitions", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
const cells = element.locator(cellQueryString);
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid-row></fast-data-grid-row>
`;
@ -176,14 +190,20 @@ test.describe("DataGridRow", () => {
await expect(cells).toHaveCount(3);
});
test("should emit a 'rowselectionchange' event when clicked with disableClickSelect disabled", async () => {
await root.evaluate((node: FASTDataGridRow) => {
test("should emit a 'rowselectionchange' event when clicked with disableClickSelect disabled", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate((node: FASTDataGridRow) => {
node.innerHTML = /* html */ `
<fast-data-grid-row>
<fast-data-grid-cell ></fast-data-grid-cell>
</fast-data-grid-row>
`;
node.disableClickSelect = false;
node["disableClickSelect"] = false;
});
const wasInvoked = await Promise.all([
@ -196,14 +216,20 @@ test.describe("DataGridRow", () => {
expect(wasInvoked).toBeTruthy;
});
test("should not emit a 'rowselectionchange' event when clicked with disableClickSelect disabled", async () => {
await root.evaluate((node: FASTDataGridRow) => {
test("should not emit a 'rowselectionchange' event when clicked with disableClickSelect disabled", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate((node: FASTDataGridRow) => {
node.innerHTML = /* html */ `
<fast-data-grid-row>
<fast-data-grid-cell ></fast-data-grid-cell>
</fast-data-grid-row>
`;
node.disableClickSelect = true;
node["disableClickSelect"] = true;
});
const wasInvoked = await Promise.all([
@ -216,14 +242,20 @@ test.describe("DataGridRow", () => {
expect(wasInvoked).toBeFalsy;
});
test("should emit a 'rowselectionchange' event when space key is pressed with disableClickSelect disabled", async () => {
await root.evaluate((node: FASTDataGridRow) => {
test("should emit a 'rowselectionchange' event when space key is pressed with disableClickSelect disabled", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid-row");
await page.locator("#root").evaluate((node: FASTDataGridRow) => {
node.innerHTML = /* html */ `
<fast-data-grid-row>
<fast-data-grid-cell ></fast-data-grid-cell>
</fast-data-grid-row>
`;
node.disableClickSelect = false;
node["disableClickSelect"] = false;
});
const wasInvoked = await Promise.all([

Просмотреть файл

@ -1,3 +1,4 @@
import type { ViewTemplate } from "@microsoft/fast-element";
import {
children,
elements,
@ -5,8 +6,8 @@ import {
html,
slotted,
} from "@microsoft/fast-element";
import type { ViewTemplate } from "@microsoft/fast-element";
import { tagFor, TemplateElementDependency } from "../patterns/index.js";
import type { TemplateElementDependency } from "../patterns/tag-for.js";
import { tagFor } from "../patterns/tag-for.js";
import type { FASTDataGridRow } from "./data-grid-row.js";
import type { ColumnDefinition } from "./data-grid.js";

Просмотреть файл

@ -1,34 +1,18 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTDataGridRow } from "./data-grid-row.js";
import type { FASTDataGrid } from "./data-grid.js";
import { DataGridRowTypes } from "./data-grid.options.js";
test.describe("Data grid", () => {
let page: Page;
let element: Locator;
let root: Locator;
const selectedRowQueryString = '[aria-selected="true"]';
const rowQueryString = "fast-data-grid-row";
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
test("should set role to 'grid'", async ({ page }) => {
await page.goto("http://localhost:6006");
element = page.locator("fast-data-grid");
const element = page.locator("fast-data-grid");
root = page.locator("#root");
await page.goto(fixtureURL("data-grid--data-grid"));
});
test.afterAll(async () => {
await page.close();
});
test("should set role to 'grid'", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid></fast-data-grid>
`;
@ -37,8 +21,12 @@ test.describe("Data grid", () => {
await expect(element).toHaveAttribute("role", "grid");
});
test("should have a tabIndex of 0 by default", async () => {
await root.evaluate(node => {
test("should have a tabIndex of 0 by default", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid></fast-data-grid>
`;
@ -47,8 +35,12 @@ test.describe("Data grid", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should have a tabIndex of -1 when no-tabbing is true", async () => {
await root.evaluate(node => {
test("should have a tabIndex of -1 when no-tabbing is true", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid no-tabbing></fast-data-grid>
`;
@ -57,8 +49,12 @@ test.describe("Data grid", () => {
await expect(element).toHaveAttribute("tabindex", "-1");
});
test("should have a tabIndex of -1 when a cell is focused", async () => {
await root.evaluate(node => {
test("should have a tabIndex of -1 when a cell is focused", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid>
<fast-data-grid-row>
@ -74,8 +70,14 @@ test.describe("Data grid", () => {
await expect(element).toHaveAttribute("tabindex", "-1");
});
test("should generate a basic grid with a row header based on rowsData only", async () => {
await root.evaluate(node => {
test("should generate a basic grid with a row header based on rowsData only", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid></fast-data-grid>
`;
@ -129,8 +131,14 @@ test.describe("Data grid", () => {
await expect(row3Cells.nth(1)).toHaveText("Person 2");
});
test("should not generate a header when `generateHeader` is `none`", async () => {
await root.evaluate(node => {
test("should not generate a header when `generateHeader` is `none`", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none"></fast-data-grid>
`;
@ -172,8 +180,12 @@ test.describe("Data grid", () => {
await expect(row2Cells.nth(1)).toHaveText("Person 2");
});
test("should not generate a header when rowsData is empty", async () => {
await root.evaluate(node => {
test("should not generate a header when rowsData is empty", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const dataGrid = document.createElement("fast-data-grid") as FASTDataGrid;
@ -188,8 +200,14 @@ test.describe("Data grid", () => {
await expect(rows).toHaveCount(0);
});
test("should generate a sticky header when generateHeader is set to 'sticky'", async () => {
await root.evaluate(node => {
test("should generate a sticky header when generateHeader is set to 'sticky'", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="sticky"></fast-data-grid>
`;
@ -243,8 +261,12 @@ test.describe("Data grid", () => {
await expect(row3Cells.nth(1)).toHaveText("Person 2");
});
test("should set the row index attribute of child rows'", async () => {
await root.evaluate(node => {
test("should set the row index attribute of child rows'", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="sticky"></fast-data-grid>
`;
@ -269,8 +291,12 @@ test.describe("Data grid", () => {
}
});
test("should move focus with up/down arrow key strokes", async () => {
await root.evaluate(node => {
test("should move focus with up/down arrow key strokes", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none"></fast-data-grid>
`;
@ -317,8 +343,14 @@ test.describe("Data grid", () => {
await expect(row2Cells.nth(0)).toBeFocused();
});
test("should move focus to first/last cells with ctrl + home/end key strokes", async () => {
await root.evaluate(node => {
test("should move focus to first/last cells with ctrl + home/end key strokes", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none"></fast-data-grid>
`;
@ -351,8 +383,14 @@ test.describe("Data grid", () => {
await expect(firstCell).toBeFocused();
});
test("should move focus by setting the `focusRowIndex` property", async () => {
await root.evaluate(node => {
test("should move focus by setting the `focusRowIndex` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none"></fast-data-grid>
`;
@ -393,8 +431,12 @@ test.describe("Data grid", () => {
await expect(cells.nth(0)).toBeFocused();
});
test("should move focus by setting `focusColumnIndex`", async () => {
await root.evaluate(node => {
test("should move focus by setting `focusColumnIndex`", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none"></fast-data-grid>
`;
@ -435,8 +477,12 @@ test.describe("Data grid", () => {
await expect(cells.nth(0)).toBeFocused();
});
test("should scroll into view on focus", async () => {
await root.evaluate(node => {
test("should scroll into view on focus", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" style="height:100px; overflow-y: scroll;">
<fast-data-grid-row style="height:100px;">
@ -461,13 +507,19 @@ test.describe("Data grid", () => {
await cells.nth(0).focus();
await expect(element).toHaveJSProperty("scrollTop", 0);
await cells.nth(1).focus();
await expect(element).toHaveJSProperty("scrollTop", 100);
expect(await element.evaluate(node => node.scrollTop)).toBeCloseTo(100, -1);
await cells.nth(2).focus();
await expect(element).toHaveJSProperty("scrollTop", 200);
expect(await element.evaluate(node => node.scrollTop)).toBeCloseTo(200, -1);
});
test("should not apply initial selection in default 'none' selection mode", async () => {
await root.evaluate(node => {
test("should not apply initial selection in default 'none' selection mode", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" initial-row-selection="0" selection-mode="none">
<fast-data-grid-row>
@ -486,8 +538,14 @@ test.describe("Data grid", () => {
await expect(element).toHaveJSProperty("selectedRowIndexes", []);
});
test("should apply initial selection in 'single-row' selection mode", async () => {
await root.evaluate(node => {
test("should apply initial selection in 'single-row' selection mode", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" initial-row-selection="0" selection-mode="single-row">
<fast-data-grid-row>
@ -506,8 +564,14 @@ test.describe("Data grid", () => {
await expect(element).toHaveJSProperty("selectedRowIndexes", [0]);
});
test("should apply initial selection in 'multi-row' selection mode", async () => {
await root.evaluate(node => {
test("should apply initial selection in 'multi-row' selection mode", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" initial-row-selection="0,1" selection-mode="multi-row">
<fast-data-grid-row>
@ -526,10 +590,16 @@ test.describe("Data grid", () => {
await expect(element).toHaveJSProperty("selectedRowIndexes", [0, 1]);
});
test("should apply user set selection in 'single-row' selection mode", async () => {
test("should apply user set selection in 'single-row' selection mode", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const selectedRows = element.locator(selectedRowQueryString);
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" initial-row-selection="0" selection-mode="single-row">
<fast-data-grid-row>
@ -561,10 +631,16 @@ test.describe("Data grid", () => {
await expect(selectedRows).toHaveCount(1);
});
test("should apply user set selection in 'multi-row' selection mode", async () => {
test("should apply user set selection in 'multi-row' selection mode", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const selectedRows = element.locator(selectedRowQueryString);
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" initial-row-selection="0" selection-mode="multi-row">
<fast-data-grid-row>
@ -596,10 +672,14 @@ test.describe("Data grid", () => {
await expect(selectedRows).toHaveCount(3);
});
test("should not allow selection of header row by default", async () => {
test("should not allow selection of header row by default", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const selectedRows = element.locator(selectedRowQueryString);
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" initial-row-selection="0" selection-mode="single-row">
<fast-data-grid-row row-type="header">
@ -619,13 +699,18 @@ test.describe("Data grid", () => {
await expect(selectedRows).toHaveCount(0);
});
test("should select and deselect rows with space bar + shift keys", async () => {
test("should select and deselect rows with space bar + shift keys", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const rows = element.locator(rowQueryString);
const selectedRows = element.locator(selectedRowQueryString);
const firstCell = rows.nth(0).locator("fast-data-grid-cell").nth(0);
const lastCell = rows.nth(2).locator("fast-data-grid-cell").nth(2);
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" selection-mode="single-row"></fast-data-grid>
`;
@ -653,13 +738,16 @@ test.describe("Data grid", () => {
await expect(element).toHaveJSProperty("selectedRowIndexes", [2]);
});
test("should select and deselect rows with a click", async () => {
test("should select and deselect rows with a click", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const rows = element.locator(rowQueryString);
const selectedRows = element.locator(selectedRowQueryString);
const firstCell = rows.nth(0).locator("fast-data-grid-cell").nth(0);
const lastCell = rows.nth(2).locator("fast-data-grid-cell").nth(2);
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" selection-mode="single-row"></fast-data-grid>
`;
@ -685,13 +773,17 @@ test.describe("Data grid", () => {
await expect(element).toHaveJSProperty("selectedRowIndexes", [2]);
});
test("should select/deselect all in row multi-select with a ctrl + a", async () => {
test("should select/deselect all in row multi-select with a ctrl + a", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const selectedRows = element.locator(selectedRowQueryString);
const rows = element.locator(rowQueryString);
const firstCell = rows.nth(0).locator("fast-data-grid-cell").nth(0);
await firstCell.focus();
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" selection-mode="multi-row"></fast-data-grid>
`;
@ -713,14 +805,18 @@ test.describe("Data grid", () => {
await expect(selectedRows).toHaveCount(0);
});
test("should select/deselect multiple rows with shift key in multi-select mode", async () => {
test("should select/deselect multiple rows with shift key in multi-select mode", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const selectedRows = element.locator(selectedRowQueryString);
const rows = element.locator(rowQueryString);
const firstCell = rows.nth(0).locator("fast-data-grid-cell").nth(0);
const lastCell = rows.nth(2).locator("fast-data-grid-cell").nth(2);
await firstCell.focus();
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" selection-mode="multi-row"></fast-data-grid>
`;
@ -742,12 +838,14 @@ test.describe("Data grid", () => {
await expect(selectedRows).toHaveCount(2);
});
test("should emit an event when row selection changes", async () => {
test("should emit an event when row selection changes", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-data-grid");
const rows = element.locator(rowQueryString);
const firstCell = rows.nth(0).locator("fast-data-grid-cell").nth(0);
await firstCell.focus();
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-data-grid generate-header="none" selection-mode="multi-row"></fast-data-grid>
`;
@ -761,6 +859,8 @@ test.describe("Data grid", () => {
];
});
await firstCell.focus();
const wasInvoked = await Promise.all([
element.evaluate(node =>
node.addEventListener("selectionchange", () => true)

Просмотреть файл

@ -1,6 +1,7 @@
import { children, elements, ElementViewTemplate, html } from "@microsoft/fast-element";
import type { ViewTemplate } from "@microsoft/fast-element";
import { tagFor, TemplateElementDependency } from "../patterns/tag-for.js";
import { children, elements, ElementViewTemplate, html } from "@microsoft/fast-element";
import type { TemplateElementDependency } from "../patterns/tag-for.js";
import { tagFor } from "../patterns/tag-for.js";
import type { FASTDataGrid } from "./data-grid.js";
/**

Просмотреть файл

@ -1,4 +1,12 @@
export { DataGridOptions, dataGridTemplate } from "./data-grid.template.js";
export { FASTDataGridCell } from "./data-grid-cell.js";
export type {
DataGridCellTypes,
defaultCellFocusTargetCallback,
} from "./data-grid-cell.js";
export { dataGridCellTemplate } from "./data-grid-cell.template.js";
export { FASTDataGridRow } from "./data-grid-row.js";
export { dataGridRowTemplate } from "./data-grid-row.template.js";
export type { CellItemTemplateOptions } from "./data-grid-row.template.js";
export {
ColumnDefinition,
DataGridRowTypes,
@ -7,14 +15,5 @@ export {
FASTDataGrid,
GenerateHeaderOptions,
} from "./data-grid.js";
export {
dataGridRowTemplate,
CellItemTemplateOptions,
} from "./data-grid-row.template.js";
export { FASTDataGridRow } from "./data-grid-row.js";
export { dataGridCellTemplate } from "./data-grid-cell.template.js";
export {
DataGridCellTypes,
defaultCellFocusTargetCallback,
FASTDataGridCell,
} from "./data-grid-cell.js";
export { dataGridTemplate } from "./data-grid.template.js";
export type { DataGridOptions } from "./data-grid.template.js";

Просмотреть файл

@ -5,9 +5,7 @@ import type {
Observable as FASTObservable,
Updates as FASTUpdates,
} from "@microsoft/fast-element";
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { DesignTokenResolver } from "./core/design-token-node.js";
import type {
CSSDesignToken as CSSDesignTokenImpl,
@ -48,26 +46,21 @@ function uniqueTokenName(): string {
// Test utilities
test.describe("A DesignToken", () => {
let page: Page;
let root: Locator;
test.beforeEach(async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
root = page.locator("#root");
await page.goto(fixtureURL("debug-designtoken--design-token"));
await page.evaluate(() => DesignToken.registerDefaultStyleTarget());
});
test.afterAll(async () => {
await page.evaluate(() => DesignToken.registerDefaultStyleTarget());
await page.close();
await page.evaluate(() => {
DesignToken.registerDefaultStyleTarget();
});
});
test.afterEach(async () => {
await page.evaluate(() => cleanup());
test.afterEach(async ({ page }) => {
await page.evaluate(() => {
cleanup();
});
});
test("should support declared types", async () => {
test("should support declared types", async ({ page }) => {
await page.evaluate((name: string) => {
const number = DesignToken.create<number>(name);
const nil = DesignToken.create<null>(name);
@ -81,7 +74,15 @@ test.describe("A DesignToken", () => {
});
test.describe("should have a create method", () => {
test("that creates a CSSDesignToken when invoked with a string value", async () => {
test("that creates a CSSDesignToken when invoked with a string value", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.evaluate(() => {
DesignToken.registerDefaultStyleTarget();
});
const result = await page.evaluate(
(name: string) => DesignToken.create(name) instanceof CSSDesignToken,
uniqueTokenName()
@ -89,7 +90,16 @@ test.describe("A DesignToken", () => {
expect(result).toBe(true);
});
test("that creates a CSSDesignToken when invoked with a CSSDesignTokenConfiguration", async () => {
test("that creates a CSSDesignToken when invoked with a CSSDesignTokenConfiguration", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.evaluate(() => {
DesignToken.registerDefaultStyleTarget();
});
const result = await page.evaluate(
(name: string) =>
DesignToken.create({
@ -100,25 +110,31 @@ test.describe("A DesignToken", () => {
);
expect(result).toBe(true);
});
test("that creates a DesignToken when invoked with a DesignTokenConfiguration", async () => {
const result = await page.evaluate(
(name: string) => DesignToken.create({ name }) instanceof CSSDesignToken,
uniqueTokenName()
);
test("that creates a DesignToken when invoked with a DesignTokenConfiguration", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const result = await page.evaluate((name: string) => {
DesignToken.registerDefaultStyleTarget();
const token = DesignToken.create({ name });
return token instanceof CSSDesignToken;
}, uniqueTokenName());
expect(result).toBe(false);
});
});
test.describe("that is not a CSSDesignToken", () => {
test("should not have a cssCustomProperty property", async () => {
test("should not have a cssCustomProperty property", async ({ page }) => {
const result = await page.evaluate((name: string) => {
return "cssCustomProperty" in DesignToken.create<number>({ name });
}, uniqueTokenName());
expect(result).toEqual(false);
});
test("should not have a cssVar property", async () => {
test("should not have a cssVar property", async ({ page }) => {
const result = await page.evaluate((name: string) => {
return "cssVar" in DesignToken.create<number>({ name });
}, uniqueTokenName());
@ -126,7 +142,9 @@ test.describe("A DesignToken", () => {
});
});
test.describe("getting and setting a simple value", () => {
test("should throw if the token value has never been set on the element or its ancestors", async () => {
test("should throw if the token value has never been set on the element or its ancestors", async ({
page,
}) => {
const result = await page.evaluate((name: string) => {
const target = addElement();
const token = DesignToken.create<number>(name);
@ -136,7 +154,9 @@ test.describe("A DesignToken", () => {
expect(result).toBe(true);
});
test("should return the value set for the element if one has been set", async () => {
test("should return the value set for the element if one has been set", async ({
page,
}) => {
const result = await page.evaluate((name: string) => {
const target = addElement();
const token = DesignToken.create<number>(name);
@ -147,7 +167,9 @@ test.describe("A DesignToken", () => {
expect(result).toEqual(12);
});
test("should return the value set for an ancestor if a value has not been set for the target", async () => {
test("should return the value set for an ancestor if a value has not been set for the target", async ({
page,
}) => {
const result = await page.evaluate((name: string) => {
const ancestor = addElement();
const target = addElement(ancestor);
@ -159,7 +181,9 @@ test.describe("A DesignToken", () => {
expect(result).toEqual(12);
});
test("sound return the nearest ancestor's value after an intermediary value is set where no value was set prior", async () => {
test("sound return the nearest ancestor's value after an intermediary value is set where no value was set prior", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -179,7 +203,9 @@ test.describe("A DesignToken", () => {
).toEqual([12, 14]);
});
test("should return the new ancestor's value after being re-parented", async () => {
test("should return the new ancestor's value after being re-parented", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -202,7 +228,9 @@ test.describe("A DesignToken", () => {
).toEqual([12, 14]);
});
test("should persist explicitly set value even if it matches the inherited value", async () => {
test("should persist explicitly set value even if it matches the inherited value", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -224,7 +252,7 @@ test.describe("A DesignToken", () => {
).toEqual([12, 12]);
});
test("should support getting and setting falsey values", async () => {
test("should support getting and setting falsey values", async ({ page }) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -250,7 +278,9 @@ test.describe("A DesignToken", () => {
});
test.describe("that is a CSSDesignToken", () => {
test("should set the CSS custom property for the element", async () => {
test("should set the CSS custom property for the element", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const target = addElement();
@ -263,7 +293,7 @@ test.describe("A DesignToken", () => {
})
).toBe("12");
});
test("should be a CSSDirective", async () => {
test("should be a CSSDirective", async ({ page }) => {
expect(
await page.evaluate(async () => {
const token = DesignToken.create<number>(
@ -286,7 +316,7 @@ test.describe("A DesignToken", () => {
).toBe("12");
});
test("should inherit CSS custom property from ancestor", async () => {
test("should inherit CSS custom property from ancestor", async ({ page }) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -312,7 +342,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", "14"]);
});
test("should set CSS custom property for element if value stops matching inherited value", async () => {
test("should set CSS custom property for element if value stops matching inherited value", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -340,7 +372,9 @@ test.describe("A DesignToken", () => {
});
});
test.describe("that is not a CSSDesignToken", () => {
test("should not set a CSS custom property for the element", async () => {
test("should not set a CSS custom property for the element", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const name = uniqueTokenName();
@ -357,7 +391,7 @@ test.describe("A DesignToken", () => {
});
test.describe("getting and setting derived values", () => {
test("should get the return value of a derived value", async () => {
test("should get the return value of a derived value", async ({ page }) => {
const name = uniqueTokenName();
expect(
await page.evaluate((name: string) => {
@ -369,7 +403,9 @@ test.describe("A DesignToken", () => {
}, name)
).toBe(12);
});
test("should get an updated value when observable properties used in a derived property are changed", async () => {
test("should get an updated value when observable properties used in a derived property are changed", async ({
page,
}) => {
const name = uniqueTokenName();
expect(
await page.evaluate(async (name: string) => {
@ -392,7 +428,9 @@ test.describe("A DesignToken", () => {
).toEqual([12, 14]);
});
test("should get an updated value when other design tokens used in a derived property are changed", async () => {
test("should get an updated value when other design tokens used in a derived property are changed", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const target = addElement();
@ -412,7 +450,9 @@ test.describe("A DesignToken", () => {
})
).toEqual([12, 14]);
});
test("should use the closest value of a dependent token when getting a token for a target", async () => {
test("should use the closest value of a dependent token when getting a token for a target", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const ancestor = addElement();
@ -430,7 +470,9 @@ test.describe("A DesignToken", () => {
).toBe(12);
});
test("should update value of a dependent token when getting a token for a target", async () => {
test("should update value of a dependent token when getting a token for a target", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -455,7 +497,9 @@ test.describe("A DesignToken", () => {
).toEqual([12, 14]);
});
test("should get an updated value when a used design token is set for a node closer to the target", async () => {
test("should get an updated value when a used design token is set for a node closer to the target", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -479,7 +523,9 @@ test.describe("A DesignToken", () => {
});
test.describe("that is a CSSDesignToken", () => {
test("should set a CSS custom property equal to the resolved value of a derived token value", async () => {
test("should set a CSS custom property equal to the resolved value of a derived token value", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const target = addElement();
@ -494,7 +540,9 @@ test.describe("A DesignToken", () => {
})
).toBe("12");
});
test("should set a CSS custom property equal to the resolved value of a derived token value with a dependent token", async () => {
test("should set a CSS custom property equal to the resolved value of a derived token value with a dependent token", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const target = addElement();
@ -513,7 +561,9 @@ test.describe("A DesignToken", () => {
).toBe("12");
});
test("should update a CSS custom property to the resolved value of a derived token value with a dependent token when the dependent token changes", async () => {
test("should update a CSS custom property to the resolved value of a derived token value with a dependent token when the dependent token changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -544,7 +594,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", "14"]);
});
test("should set a CSS custom property equal to the resolved value for an element of a derived token value with a dependent token", async () => {
test("should set a CSS custom property equal to the resolved value for an element of a derived token value with a dependent token", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -574,7 +626,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", "14"]);
});
test("should set a CSS custom property equal to the resolved value for an element in a shadow DOM of a derived token value with a dependent token", async () => {
test("should set a CSS custom property equal to the resolved value for an element in a shadow DOM of a derived token value with a dependent token", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -606,7 +660,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", "14"]);
});
test("should set a CSS custom property equal to the resolved value for both elements for which a dependent token is set when setting a derived token value", async () => {
test("should set a CSS custom property equal to the resolved value for both elements for which a dependent token is set when setting a derived token value", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -636,7 +692,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", "14"]);
});
test("should revert a CSS custom property back to a previous value when the Design Token value is reverted", async () => {
test("should revert a CSS custom property back to a previous value when the Design Token value is reverted", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -674,7 +732,7 @@ test.describe("A DesignToken", () => {
});
test.describe("that is not a CSSDesignToken", () => {
test("should not emit a CSS custom property", async () => {
test("should not emit a CSS custom property", async ({ page }) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -692,7 +750,7 @@ test.describe("A DesignToken", () => {
});
});
test("should support getting and setting falsey values", async () => {
test("should support getting and setting falsey values", async ({ page }) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -718,7 +776,7 @@ test.describe("A DesignToken", () => {
});
test.describe("getting and setting a token value", () => {
test("should retrieve the value of the token it was set to", async () => {
test("should retrieve the value of the token it was set to", async ({ page }) => {
expect(
await page.evaluate(() => {
const tokenA = DesignToken.create<number>(uniqueTokenName());
@ -733,7 +791,9 @@ test.describe("A DesignToken", () => {
).toBe(12);
});
test("should update the value of the token it was set to when the token's value changes", async () => {
test("should update the value of the token it was set to when the token's value changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -753,7 +813,9 @@ test.describe("A DesignToken", () => {
})
).toEqual([12, 14]);
});
test("should update the derived value of the token when a dependency of the derived value changes", async () => {
test("should update the derived value of the token when a dependency of the derived value changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -778,7 +840,7 @@ test.describe("A DesignToken", () => {
});
test.describe("that is a CSSDesignToken", () => {
test("should emit a CSS custom property", async () => {
test("should emit a CSS custom property", async ({ page }) => {
expect(
await page.evaluate(async () => {
const tokenA = DesignToken.create<number>("token-a");
@ -795,7 +857,9 @@ test.describe("A DesignToken", () => {
})
);
});
test("should update the emitted CSS custom property when the token's value changes", async () => {
test("should update the emitted CSS custom property when the token's value changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -827,7 +891,9 @@ test.describe("A DesignToken", () => {
})
).toEqual(["12", "14"]);
});
test("should update the emitted CSS custom property of a token assigned a derived value when the token dependency changes", async () => {
test("should update the emitted CSS custom property of a token assigned a derived value when the token dependency changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -861,7 +927,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", "14"]);
});
test("should support accessing the token being assigned from the derived value, resolving to a parent value", async () => {
test("should support accessing the token being assigned from the derived value, resolving to a parent value", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -881,7 +949,9 @@ test.describe("A DesignToken", () => {
).toEqual([12, 24]);
});
});
test("should update the CSS custom property of a derived token with a dependency that is a derived token that depends on a third token", async () => {
test("should update the CSS custom property of a derived token with a dependency that is a derived token that depends on a third token", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -921,7 +991,9 @@ test.describe("A DesignToken", () => {
});
});
test.describe("deleting simple values", () => {
test("should throw when deleted and no parent token value is set", async () => {
test("should throw when deleted and no parent token value is set", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const results = [];
@ -939,7 +1011,7 @@ test.describe("A DesignToken", () => {
})
).toEqual([12, true]);
});
test("should allow getting a value that was set upstream", async () => {
test("should allow getting a value that was set upstream", async ({ page }) => {
expect(
await page.evaluate(() => {
const results = [];
@ -961,7 +1033,9 @@ test.describe("A DesignToken", () => {
});
});
test.describe("deleting derived values", () => {
test("should throw when deleted and no parent token value is set", async () => {
test("should throw when deleted and no parent token value is set", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const results = [];
@ -979,7 +1053,7 @@ test.describe("A DesignToken", () => {
})
).toEqual([12, true]);
});
test("should allow getting a value that was set upstream", async () => {
test("should allow getting a value that was set upstream", async ({ page }) => {
expect(
await page.evaluate(() => {
const results = [];
@ -1000,7 +1074,7 @@ test.describe("A DesignToken", () => {
).toEqual([14, 12]);
});
test("should cause dependent tokens to re-evaluate", async () => {
test("should cause dependent tokens to re-evaluate", async ({ page }) => {
expect(
await page.evaluate(() => {
const results = [];
@ -1024,7 +1098,9 @@ test.describe("A DesignToken", () => {
});
});
test.describe("when used as a CSSDirective", () => {
test("should set a CSS custom property for the element when the token is set for the element", async () => {
test("should set a CSS custom property for the element when the token is set for the element", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const target = addElement();
@ -1042,7 +1118,9 @@ test.describe("A DesignToken", () => {
})
).toBe("12px");
});
test("should set a CSS custom property for the element when the token is set for an ancestor element", async () => {
test("should set a CSS custom property for the element when the token is set for an ancestor element", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const parent = addElement();
@ -1064,7 +1142,9 @@ test.describe("A DesignToken", () => {
});
test.describe("with a default value set", () => {
test("should return the default value if no value is set for a target", async () => {
test("should return the default value if no value is set for a target", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -1074,7 +1154,9 @@ test.describe("A DesignToken", () => {
})
).toBe(2);
});
test("should return the default value for a descendent if no value is set for a target", async () => {
test("should return the default value for a descendent if no value is set for a target", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const parent = addElement();
@ -1086,7 +1168,9 @@ test.describe("A DesignToken", () => {
})
).toBe(2);
});
test("should return the value set and not the default if value is set", async () => {
test("should return the value set and not the default if value is set", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -1098,7 +1182,9 @@ test.describe("A DesignToken", () => {
})
).toBe(2);
});
test("should get a new default value if a new default is provided", async () => {
test("should get a new default value if a new default is provided", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -1110,7 +1196,9 @@ test.describe("A DesignToken", () => {
})
).toBe(4);
});
test("should return the default value if retrieved for an element that has not been connected", async () => {
test("should return the default value if retrieved for an element that has not been connected", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const token = DesignToken.create<number>(
@ -1122,7 +1210,9 @@ test.describe("A DesignToken", () => {
})
).toBe(12);
});
test("should set a derived value that uses a token's default value prior to connection", async () => {
test("should set a derived value that uses a token's default value prior to connection", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const dependency = DesignToken.create<number>(
@ -1137,7 +1227,9 @@ test.describe("A DesignToken", () => {
})
).toBe(false);
});
test("should delete a derived value that uses a token's default value prior to connection", async () => {
test("should delete a derived value that uses a token's default value prior to connection", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const dependency = DesignToken.create<number>(
@ -1154,7 +1246,9 @@ test.describe("A DesignToken", () => {
});
test.describe("with subscribers", () => {
test("should notify an un-targeted subscriber when the value changes for any element", async () => {
test("should notify an un-targeted subscriber when the value changes for any element", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const results: any = [];
@ -1200,7 +1294,9 @@ test.describe("A DesignToken", () => {
[true, true, true],
]);
});
test("should notify a target-subscriber if the value is changed for the provided target", async () => {
test("should notify a target-subscriber if the value is changed for the provided target", async ({
page,
}) => {
expect(
await page.evaluate(() => {
const results = [];
@ -1225,7 +1321,7 @@ test.describe("A DesignToken", () => {
).toEqual([1, 2]);
});
test("should not notify a subscriber after unsubscribing", async () => {
test("should not notify a subscriber after unsubscribing", async ({ page }) => {
expect(
await page.evaluate(() => {
const target = addElement();
@ -1245,7 +1341,9 @@ test.describe("A DesignToken", () => {
).toBe(0);
});
test("should infer DesignToken and CSSDesignToken token types on subscription record", async () => {
test("should infer DesignToken and CSSDesignToken token types on subscription record", async ({
page,
}) => {
await page.evaluate(() => {
type AssertCSSDesignToken<T> = T extends CSSDesignTokenImpl<any>
? T
@ -1267,7 +1365,9 @@ test.describe("A DesignToken", () => {
});
});
test("should notify a subscriber when a dependency of a subscribed token changes", async () => {
test("should notify a subscriber when a dependency of a subscribed token changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const tokenA = DesignToken.create<number>(uniqueTokenName());
@ -1290,7 +1390,9 @@ test.describe("A DesignToken", () => {
).toBe(1);
});
test("should notify a subscriber when a dependency of a dependency of a subscribed token changes", async () => {
test("should notify a subscriber when a dependency of a dependency of a subscribed token changes", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const tokenA = DesignToken.create<number>(uniqueTokenName());
@ -1315,7 +1417,9 @@ test.describe("A DesignToken", () => {
).toBe(1);
});
test("should notify a subscriber when a dependency changes for an element down the DOM tree", async () => {
test("should notify a subscriber when a dependency changes for an element down the DOM tree", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const tokenA = DesignToken.create<number>(uniqueTokenName());
@ -1339,7 +1443,9 @@ test.describe("A DesignToken", () => {
})
).toBe(1);
});
test("should notify a subscriber when a static-value dependency of subscribed token changes for a parent of the subscription target", async () => {
test("should notify a subscriber when a static-value dependency of subscribed token changes for a parent of the subscription target", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -1368,7 +1474,9 @@ test.describe("A DesignToken", () => {
})
).toEqual([1, 14]);
});
test("should notify a subscriber when a derived-value dependency of subscribed token changes for a parent of the subscription target", async () => {
test("should notify a subscriber when a derived-value dependency of subscribed token changes for a parent of the subscription target", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -1396,7 +1504,9 @@ test.describe("A DesignToken", () => {
})
).toEqual([1, 14]);
});
test("should notify a subscriber when a dependency of subscribed token changes for a parent of the subscription target", async () => {
test("should notify a subscriber when a dependency of subscribed token changes for a parent of the subscription target", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const tokenA = DesignToken.create<number>(uniqueTokenName());
@ -1425,7 +1535,9 @@ test.describe("A DesignToken", () => {
});
test.describe("with root registration", () => {
test("should not emit CSS custom properties for the default value if a root is not registered", async () => {
test("should not emit CSS custom properties for the default value if a root is not registered", async ({
page,
}) => {
expect(
await page.evaluate(() => {
DesignToken.unregisterDefaultStyleTarget();
@ -1439,7 +1551,9 @@ test.describe("A DesignToken", () => {
);
});
test("should emit CSS custom properties for the default value when a design token root is registered", async () => {
test("should emit CSS custom properties for the default value when a design token root is registered", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const token = DesignToken.create<number>(
@ -1456,7 +1570,9 @@ test.describe("A DesignToken", () => {
).toBe("12");
});
test("should remove emitted CSS custom properties for a root when the root is deregistered", async () => {
test("should remove emitted CSS custom properties for a root when the root is deregistered", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const results = [];
@ -1480,7 +1596,9 @@ test.describe("A DesignToken", () => {
).toEqual(["12", ""]);
});
test("should emit CSS custom properties to an element when the element is provided as a root", async () => {
test("should emit CSS custom properties to an element when the element is provided as a root", async ({
page,
}) => {
expect(
await page.evaluate(async () => {
const token = DesignToken.create<number>(
@ -1497,7 +1615,7 @@ test.describe("A DesignToken", () => {
})
).toBe("12");
});
test("should emit CSS custom properties to multiple roots", async () => {
test("should emit CSS custom properties to multiple roots", async ({ page }) => {
expect(
await page.evaluate(async () => {
const results = [] as any;
@ -1545,7 +1663,9 @@ test.describe("A DesignToken", () => {
});
// Flakey and seems to be corrupted by other tests.
test.skip("should set properties for a PropertyTarget registered as the root", async () => {
test("should set properties for a PropertyTarget registered as the root", async ({
page,
}) => {
const results = await page.evaluate(async () => {
const results = [];
const token = DesignToken.create<number>(uniqueTokenName()).withDefault(

Просмотреть файл

@ -0,0 +1,99 @@
import { css, FASTElement, html, Observable, Updates } from "@microsoft/fast-element";
import { uniqueElementName } from "@microsoft/fast-element/testing.js";
import { CSSDesignToken, DesignToken as FASTDesignToken } from "../fast-design-token.js";
const controllerElementName = uniqueElementName("design-token-controller");
const fixtureElementName = uniqueElementName("design-token-fixture");
// Define element that can have token mutated for it
(class extends FASTElement {}).define({
name: fixtureElementName,
template: html`
<slot></slot>
`,
});
const elementCache = new Set<HTMLElement>();
// The objects required for unit-testing
// DesignToken. These get installed on the
// globalThis during story connection, and
// removed during disconnection
const requiredTestObject = {
DesignToken: FASTDesignToken,
CSSDesignToken,
uniqueTokenName() {
return uniqueElementName() + "token";
},
createElement(): FASTElement {
const element = document.createElement(fixtureElementName) as FASTElement;
elementCache.add(element);
return element;
},
addElement(parent = document.body) {
const el = requiredTestObject.createElement();
el.setAttribute("id", "id" + uniqueElementName());
parent.appendChild(el);
return el;
},
css,
threw(fn: () => void): boolean {
try {
fn();
return false;
} catch (e) {
return true;
}
},
spy(fn: () => any) {
let calls: number = 0;
const callArgs: any = [];
const f = (...args: any[]) => {
calls += 1;
callArgs[calls] = args;
};
Object.defineProperties(f, {
calls: {
get() {
return calls;
},
},
calledWith: {
value: function (n: number): any {
return callArgs[n];
},
},
});
return f;
},
Updates,
Observable,
cleanup() {
elementCache.forEach(value => {
value.parentElement?.removeChild(value);
elementCache.delete(value);
});
},
};
(class extends FASTElement {
connectedCallback(): void {
super.connectedCallback();
// Set up global variables necessary to execute unit tests
Object.entries(requiredTestObject).forEach(([key, value]) => {
Object.defineProperty(globalThis, key, { value, configurable: true });
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
// Tear down global variables set up during connectedCallback
Object.keys(requiredTestObject).forEach(key => {
delete globalThis[key];
});
}
}).define(controllerElementName);
document.body.appendChild(document.createElement(controllerElementName));

Просмотреть файл

@ -1,115 +0,0 @@
import { css, FASTElement, html, Observable, Updates } from "@microsoft/fast-element";
import { uniqueElementName } from "@microsoft/fast-element/testing.js";
import type { Meta, Story } from "../../__test__/helpers.js";
import { CSSDesignToken, DesignToken as FASTDesignToken } from "../fast-design-token.js";
export default {
title: "Debug/DesignToken",
} as Meta;
export const DesignToken: Story = () => {
const controllerElementName = uniqueElementName();
const fixtureElementName = uniqueElementName();
// Define element that can have token mutated for it
(class extends FASTElement {}).define({
name: fixtureElementName,
template: html`
<slot></slot>
`,
});
const elementCache = new Set<HTMLElement>();
// The objects required for unit-testing
// DesignToken. These get installed on the
// globalThis during story connection, and
// removed during disconnection
const requiredTestObject = {
DesignToken: FASTDesignToken,
CSSDesignToken,
uniqueTokenName() {
return uniqueElementName() + "token";
},
createElement(): FASTElement {
const element = document.createElement(fixtureElementName) as FASTElement;
elementCache.add(element);
return element;
},
addElement(parent = document.body) {
const el = requiredTestObject.createElement();
el.setAttribute("id", "id" + uniqueElementName());
parent.appendChild(el);
return el;
},
css,
threw(fn: () => void): boolean {
try {
fn();
return false;
} catch (e) {
return true;
}
},
spy(fn: () => any) {
let calls: number = 0;
const callArgs: any = [];
const f = (...args: any[]) => {
calls += 1;
callArgs[calls] = args;
};
Object.defineProperties(f, {
calls: {
get() {
return calls;
},
},
calledWith: {
value: function (n: number): any {
return callArgs[n];
},
},
});
return f;
},
Updates,
Observable,
cleanup() {
elementCache.forEach(value => {
value.parentElement?.removeChild(value);
elementCache.delete(value);
});
},
};
(class extends FASTElement {
connectedCallback(): void {
super.connectedCallback();
// Set up global variables necessary to execute unit tests
Object.entries(requiredTestObject).forEach(([key, value]) => {
Object.defineProperty(globalThis, key, { value, configurable: true });
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
// Tear down global variables set up during connectedCallback
Object.keys(requiredTestObject).forEach(key => {
delete globalThis[key];
});
}
}).define({
name: controllerElementName,
template: html`
<h1>Nothing to see here, folks.</h1>
<p>
This page is an entrypoint for DesignToken unit tests and should only be
used for programmatic purposes
</p>
`,
});
return document.createElement(controllerElementName);
};

Просмотреть файл

@ -1,42 +1,54 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTDialog } from "./index.js";
test.describe("Dialog", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
let overlay: Locator;
test("should include a role of `dialog` on the control", async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-dialog");
element = page.locator("fast-dialog");
const control = element.locator(`[role="dialog"]`);
root = page.locator("#root");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog></fast-dialog>
`;
});
control = element.locator(`[role="dialog"]`);
overlay = element.locator(".overlay");
await page.goto(fixtureURL("dialog--dialog"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of `dialog` on the control", async () => {
await expect(control).toHaveClass(/control/);
});
test('should set the `tabindex` attribute on the control to "-1" by default', async () => {
test('should set the `tabindex` attribute on the control to "-1" by default', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const control = element.locator(`[role="dialog"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog></fast-dialog>
`;
});
await expect(control).toHaveAttribute("tabindex", "-1");
});
test("should set the `hidden` attribute to equal the value of the `hidden` property", async () => {
test("should set the `hidden` attribute to equal the value of the `hidden` property", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog></fast-dialog>
`;
});
await element.evaluate((node: FASTDialog) => {
node.hidden = true;
});
@ -50,11 +62,19 @@ test.describe("Dialog", () => {
await expect(element).not.toHaveBooleanAttribute("hidden");
});
test("should set the `aria-describedby` attribute on the control when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-describedby` attribute on the control when provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const control = element.locator(`[role="dialog"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog aria-describedby="description">
<div id="description">Description</div>
<div id="description">This is a description</div>
</fast-dialog>
`;
});
@ -62,8 +82,16 @@ test.describe("Dialog", () => {
await expect(control).toHaveAttribute("aria-describedby", "description");
});
test("should set the `aria-labelledby` attribute on the dialog control when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-labelledby` attribute on the dialog control when provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const control = element.locator(`[role="dialog"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog aria-labelledby="label">
<div id="label">Label</div>
@ -74,8 +102,16 @@ test.describe("Dialog", () => {
await expect(control).toHaveAttribute("aria-labelledby", "label");
});
test("should set the `aria-label` attribute on the dialog control when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-label` attribute on the dialog control when provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const control = element.locator(`[role="dialog"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog aria-label="Label"></fast-dialog>
`;
@ -84,8 +120,16 @@ test.describe("Dialog", () => {
await expect(control).toHaveAttribute("aria-label", "Label");
});
test("should add an attribute of `aria-modal` with a value equal to the `modal` attribute", async () => {
await root.evaluate(node => {
test("should add an attribute of `aria-modal` with a value equal to the `modal` attribute", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const control = element.locator(`[role="dialog"]`);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;
@ -100,8 +144,16 @@ test.describe("Dialog", () => {
await expect(control).not.hasAttribute("aria-modal");
});
test('should add an overlay element with a `role` attribute of "presentation" when the `modal` property is true', async () => {
await root.evaluate(node => {
test('should add an overlay element with a `role` attribute of "presentation" when the `modal` property is true', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const overlay = element.locator(".overlay");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;
@ -116,8 +168,14 @@ test.describe("Dialog", () => {
await expect(overlay).toHaveCount(0);
});
test("should add an attribute of `no-focus-trap` when `noFocusTrap` is true", async () => {
await root.evaluate(node => {
test("should add an attribute of `no-focus-trap` when `noFocusTrap` is true", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog no-focus-trap></fast-dialog>
`;
@ -136,7 +194,19 @@ test.describe("Dialog", () => {
await expect(element).not.toHaveBooleanAttribute("no-focus-trap");
});
test("should add the `hidden` attribute when the `hide()` method is invoked", async () => {
test("should add the `hidden` attribute when the `hide()` method is invoked", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;
});
await element.evaluate((node: FASTDialog) => {
node.hidden = false;
});
@ -152,7 +222,19 @@ test.describe("Dialog", () => {
await expect(element).toHaveJSProperty("hidden", true);
});
test("should remove the `hidden` attribute when the `show()` method is invoked", async () => {
test("should remove the `hidden` attribute when the `show()` method is invoked", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;
});
await element.evaluate((node: FASTDialog) => {
node.hidden = true;
});
@ -170,8 +252,16 @@ test.describe("Dialog", () => {
await expect(element).not.toHaveBooleanAttribute("hidden");
});
test("should fire a 'dismiss' event when its overlay is clicked", async () => {
await root.evaluate(node => {
test("should fire a 'dismiss' event when its overlay is clicked", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const overlay = element.locator(".overlay");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;
@ -193,8 +283,14 @@ test.describe("Dialog", () => {
expect(wasDismissed).toBeTruthy();
});
test("should fire a `cancel` event when its overlay is clicked", async () => {
await root.evaluate(node => {
test("should fire a `cancel` event when its overlay is clicked", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const overlay = element.locator(".overlay");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;
@ -216,8 +312,16 @@ test.describe("Dialog", () => {
expect(wasDismissed).toBeTruthy();
});
test("should fire a 'dismiss' event when keydown is invoked on the document", async () => {
await root.evaluate(node => {
test("should fire a 'dismiss' event when keydown is invoked on the document", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-dialog");
const overlay = element.locator(".overlay");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-dialog modal></fast-dialog>
`;

Просмотреть файл

@ -1,33 +1,45 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTDisclosure } from "./disclosure.js";
test.describe("Disclosure", () => {
test.describe("States, Attributes, and Properties", () => {
let page: Page;
let element: Locator;
let summary: Locator;
test("should set the `aria-controls` attribute on the internal summary element", async ({
page,
}) => {
const element = page.locator("fast-disclosure");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const summary = element.locator("summary");
element = page.locator("fast-disclosure");
await page.goto("http://localhost:6006");
summary = element.locator("summary");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-disclosure>
<div slot="summary">Summary</div>
<div slot="content">Content</div>
</fast-disclosure>
`;
});
await page.goto(fixtureURL("disclosure--disclosure"));
});
test.afterAll(async () => {
await page.close();
});
test("should set the `aria-controls` attribute on the internal summary element", async () => {
await expect(summary).toHaveAttribute("aria-controls", "disclosure-content");
});
test("should toggle the `expanded` attribute based on the value of the `expanded` property", async () => {
test("should toggle the `expanded` attribute based on the value of the `expanded` property", async ({
page,
}) => {
const element = page.locator("fast-disclosure");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-disclosure>
<div slot="summary">Summary</div>
<div slot="content">Content</div>
</fast-disclosure>
`;
});
await expect(element).not.toHaveBooleanAttribute("expanded");
await element.evaluate((node: FASTDisclosure) => {
@ -43,7 +55,21 @@ test.describe("Disclosure", () => {
await expect(element).not.toHaveBooleanAttribute("expanded");
});
test("should set summary slot content to the value of the summary attribute", async () => {
test("should set summary slot content to the value of the summary attribute", async ({
page,
}) => {
const element = page.locator("fast-disclosure");
const summary = element.locator("summary");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-disclosure></fast-disclosure>
`;
});
const summaryContent =
"Should set the summary slot content to the value of the summary attribute";
@ -54,7 +80,22 @@ test.describe("Disclosure", () => {
await expect(summary).toHaveText(summaryContent);
});
test("should toggle the content when the `toggle()` method is invoked", async () => {
test("should toggle the content when the `toggle()` method is invoked", async ({
page,
}) => {
const element = page.locator("fast-disclosure");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-disclosure>
<div slot="summary">Summary</div>
<div slot="content">Content</div>
</fast-disclosure>
`;
});
await element.evaluate((node: FASTDisclosure) => {
node.toggle();
});
@ -68,7 +109,22 @@ test.describe("Disclosure", () => {
await expect(element).toHaveJSProperty("expanded", false);
});
test("should expand and collapse the content when the `show()` and `hide()` methods are invoked", async () => {
test("should expand and collapse the content when the `show()` and `hide()` methods are invoked", async ({
page,
}) => {
const element = page.locator("fast-disclosure");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-disclosure>
<div slot="summary">Summary</div>
<div slot="content">Content</div>
</fast-disclosure>
`;
});
await expect(element).toHaveJSProperty("expanded", false);
await element.evaluate((node: FASTDisclosure) => {
@ -84,7 +140,24 @@ test.describe("Disclosure", () => {
await expect(element).toHaveJSProperty("expanded", false);
});
test("should set the `aria-expanded` attribute on the internal summary element equal to the `expanded` property", async () => {
test("should set the `aria-expanded` attribute on the internal summary element equal to the `expanded` property", async ({
page,
}) => {
const element = page.locator("fast-disclosure");
const summary = element.locator("summary");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-disclosure>
<div slot="summary">Summary</div>
<div slot="content">Content</div>
</fast-disclosure>
`;
});
await expect(summary).toHaveAttribute("aria-expanded", "false");
await element.evaluate((node: FASTDisclosure) => {

Просмотреть файл

@ -1,30 +1,14 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import { DividerOrientation, DividerRole } from "./divider.options.js";
import type { FASTDivider } from "./index.js";
test.describe("Divider", () => {
let page: Page;
let element: Locator;
let root: Locator;
test('should set a default `role` attribute of "separator"', async ({ page }) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const element = page.locator("fast-divider");
element = page.locator("fast-divider");
root = page.locator("#root");
await page.goto(fixtureURL("divider--divider"));
});
test.afterAll(async () => {
await page.close();
});
test('should set a default `role` attribute of "separator"', async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-divider></fast-divider>
`;
@ -33,8 +17,14 @@ test.describe("Divider", () => {
await expect(element).toHaveAttribute("role", DividerRole.separator);
});
test("should set the `role` attribute equal to the role provided", async () => {
await root.evaluate(node => {
test("should set the `role` attribute equal to the role provided", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-divider");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-divider role="presentation"></fast-divider>
`;
@ -49,8 +39,14 @@ test.describe("Divider", () => {
await expect(element).toHaveAttribute("role", DividerRole.separator);
});
test("should set the `aria-orientation` attribute equal to the `orientation` value", async () => {
await root.evaluate(node => {
test("should set the `aria-orientation` attribute equal to the `orientation` value", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-divider");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-divider orientation="vertical"></fast-divider>
`;
@ -71,8 +67,14 @@ test.describe("Divider", () => {
);
});
test("should NOT set the `aria-orientation` attribute equal to the `orientation` value if the `role` is presentational", async () => {
await root.evaluate(node => {
test("should NOT set the `aria-orientation` attribute equal to the `orientation` value if the `role` is presentational", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-divider");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-divider orientation="vertical"></fast-divider>
`;

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTFlipper } from "./flipper.js";
test.describe("Flipper", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should include a role of button", async ({ page }) => {
const element = page.locator("fast-flipper");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-flipper");
root = page.locator("#root");
await page.goto(fixtureURL("flipper--flipper"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of button", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper></fast-flipper>
`;
@ -32,8 +16,12 @@ test.describe("Flipper", () => {
await expect(element).toHaveAttribute("role", "button");
});
test('should set `aria-hidden` to "true" by default', async () => {
await root.evaluate(node => {
test('should set `aria-hidden` to "true" by default', async ({ page }) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper></fast-flipper>
`;
@ -42,8 +30,14 @@ test.describe("Flipper", () => {
await expect(element).toHaveAttribute("aria-hidden", "true");
});
test("should set the `hiddenFromAT` property to true by default", async () => {
await root.evaluate(node => {
test("should set the `hiddenFromAT` property to true by default", async ({
page,
}) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper></fast-flipper>
`;
@ -52,8 +46,12 @@ test.describe("Flipper", () => {
await expect(element).toHaveJSProperty("hiddenFromAT", true);
});
test('should set the `direction` property to "next" by default', async () => {
await root.evaluate(node => {
test('should set the `direction` property to "next" by default', async ({ page }) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper></fast-flipper>
`;
@ -62,8 +60,14 @@ test.describe("Flipper", () => {
await expect(element.locator("span")).toHaveClass(/next/);
});
test("should toggle the `aria-disabled` attribute based on the value of the `disabled` property", async () => {
await root.evaluate(node => {
test("should toggle the `aria-disabled` attribute based on the value of the `disabled` property", async ({
page,
}) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper disabled></fast-flipper>
`;
@ -80,8 +84,14 @@ test.describe("Flipper", () => {
await expect(element).not.hasAttribute("aria-disabled");
});
test('should set the `tabindex` attribute to "-1" when `hiddenFromAT` is true', async () => {
await root.evaluate(node => {
test('should set the `tabindex` attribute to "-1" when `hiddenFromAT` is true', async ({
page,
}) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper></fast-flipper>
`;
@ -104,8 +114,12 @@ test.describe("Flipper", () => {
await expect(element).toHaveAttribute("tabindex", "-1");
});
test("should set a `tabindex` of 0 when `aria-hidden` is false", async () => {
await root.evaluate(node => {
test("should set a `tabindex` of 0 when `aria-hidden` is false", async ({ page }) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper aria-hidden="false"></fast-flipper>
`;
@ -120,8 +134,14 @@ test.describe("Flipper", () => {
await expect(element).toHaveAttribute("tabindex", "-1");
});
test('should render a span with a class of "next" when the `direction` attribute is "next"', async () => {
await root.evaluate(node => {
test('should render a span with a class of "next" when the `direction` attribute is "next"', async ({
page,
}) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper direction="next"></fast-flipper>
`;
@ -134,8 +154,14 @@ test.describe("Flipper", () => {
await expect(spans).toHaveClass(/next/);
});
test('should render a span with a class of "previous" when the `direction` attribute is "previous"', async () => {
await root.evaluate(node => {
test('should render a span with a class of "previous" when the `direction` attribute is "previous"', async ({
page,
}) => {
const element = page.locator("fast-flipper");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-flipper direction="previous"></fast-flipper>
`;

Просмотреть файл

@ -1,6 +1,4 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
// eslint-disable-next-line no-restricted-imports
import type {
@ -9,22 +7,12 @@ import type {
} from "./stories/form-associated.register.js";
test.describe("FormAssociated", () => {
let page: Page;
let root: Locator;
test("should have an empty string value prior to connectedCallback", async ({
page,
}) => {
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
root = page.locator("#root");
await page.goto(fixtureURL("debug--blank"));
});
test.afterAll(async () => {
await page.close();
});
test("should have an empty string value prior to connectedCallback", async () => {
const [value, currentValue] = await root.evaluate(node => {
const [value, currentValue] = await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const el = document.createElement("test-element") as FormAssociatedElement;
@ -37,8 +25,12 @@ test.describe("FormAssociated", () => {
expect(currentValue).toBe("");
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const el = document.createElement("test-element") as FormAssociatedElement;
@ -57,8 +49,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("initialValue", "foobar");
});
test("should initialize to the provided value ATTRIBUTE if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value ATTRIBUTE if set pre-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const el = document.createElement("test-element") as FormAssociatedElement;
@ -75,8 +71,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foobar");
});
test("should initialize to the provided value ATTRIBUTE if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value ATTRIBUTE if set post-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<test-element></test-element>
`;
@ -93,8 +93,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foobar");
});
test("should initialize to the provided value PROPERTY if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value PROPERTY if set pre-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const el = document.createElement("test-element") as FormAssociatedElement;
@ -111,8 +115,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foobar");
});
test("should initialize to the provided value PROPERTY if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value PROPERTY if set post-connection", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<test-element></test-element>
`;
@ -129,8 +137,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foobar");
});
test("should initialize to the initial value when initial value is assigned by extending class", async () => {
await root.evaluate(node => {
test("should initialize to the initial value when initial value is assigned by extending class", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<custom-initial-value></custom-initial-value>
`;
@ -143,8 +155,10 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foobar");
});
test("should communicate initial value to the parent form", async () => {
await root.evaluate(node => {
test("should communicate initial value to the parent form", async ({ page }) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<custom-initial-value name="test"></custom-initial-value>
@ -164,8 +178,12 @@ test.describe("FormAssociated", () => {
});
test.describe("changes:", () => {
test("setting value ATTRIBUTE should set value if value PROPERTY has not been explicitly set", async () => {
await root.evaluate(node => {
test("setting value ATTRIBUTE should set value if value PROPERTY has not been explicitly set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<test-element></test-element>
`;
@ -190,8 +208,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "bar");
});
test("setting value ATTRIBUTE should not set value if value PROPERTY has been explicitly set", async () => {
await root.evaluate(node => {
test("setting value ATTRIBUTE should not set value if value PROPERTY has been explicitly set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<test-element></test-element>
`;
@ -216,8 +238,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foo");
});
test("setting value ATTRIBUTE should set parent form value if value PROPERTY has not been explicitly set", async () => {
await root.evaluate(node => {
test("setting value ATTRIBUTE should set parent form value if value PROPERTY has not been explicitly set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<test-element name="test"></test-element>
@ -254,8 +280,10 @@ test.describe("FormAssociated", () => {
).toBe("bar");
});
test("setting value PROPERTY should set parent form value", async () => {
await root.evaluate(node => {
test("setting value PROPERTY should set parent form value", async ({ page }) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<test-element name="test"></test-element>
@ -292,8 +320,12 @@ test.describe("FormAssociated", () => {
).toBe("bar");
});
test("assigning the currentValue property should set the controls value property to the same value", async () => {
await root.evaluate(node => {
test("assigning the currentValue property should set the controls value property to the same value", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<test-element></test-element>
`;
@ -314,8 +346,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveJSProperty("currentValue", "foo");
});
test("setting the current-value property should set the controls value property to the same value", async () => {
await root.evaluate(node => {
test("setting the current-value property should set the controls value property to the same value", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<test-element></test-element>
`;
@ -338,8 +374,12 @@ test.describe("FormAssociated", () => {
});
test.describe("when the owning form's reset() method is invoked", () => {
test("should reset it's value property to an empty string if no value attribute is set", async () => {
await root.evaluate(node => {
test("should reset it's value property to an empty string if no value attribute is set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<test-element name="test"></test-element>
@ -372,8 +412,12 @@ test.describe("FormAssociated", () => {
await expect(element).not.hasAttribute("value");
});
test("should reset it's value property to the value of the value attribute if it is set", async () => {
await root.evaluate(node => {
test("should reset it's value property to the value of the value attribute if it is set", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<test-element name="test" value="attr-value"></test-element>
@ -412,8 +456,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveAttribute("value", "attr-value");
});
test("should put the control into a clean state, where modifcations to the `value` attribute update the `value` property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where modifcations to the `value` attribute update the `value` property prior to user or programmatic interaction", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<test-element name="test" value="attr-value"></test-element>
@ -464,8 +512,12 @@ test.describe("FormAssociated", () => {
});
test.describe("CheckableFormAssociated", () => {
test("should have a 'checked' property that is initialized to false", async () => {
await root.evaluate(node => {
test("should have a 'checked' property that is initialized to false", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<checkable-form-associated></checkable-form-associated>
`;
@ -480,8 +532,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveAttribute("current-checked", "false");
});
test("should align the `currentChecked` property and `current-checked` attribute with `checked` property changes", async () => {
await root.evaluate(node => {
test("should align the `currentChecked` property and `current-checked` attribute with `checked` property changes", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<checkable-form-associated></checkable-form-associated>
`;
@ -516,8 +572,12 @@ test.describe("FormAssociated", () => {
await expect(element).toHaveAttribute("current-checked", "false");
});
test("should align the `checked` property and `current-checked` attribute with `currentChecked` property changes", async () => {
await root.evaluate(node => {
test("should align the `checked` property and `current-checked` attribute with `currentChecked` property changes", async ({
page,
}) => {
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<checkable-form-associated></checkable-form-associated>
`;

Просмотреть файл

@ -1,60 +1,75 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTHorizontalScroll } from "./horizontal-scroll.js";
test.describe("HorizontalScroll", () => {
let page: Page;
let element: Locator;
let root: Locator;
let cards: Locator;
let scrollNext: Locator;
let scrollPrevious: Locator;
let scrollView: Locator;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
element = page.locator("fast-horizontal-scroll");
root = page.locator("#root");
cards = element.locator("fast-card");
scrollNext = element.locator(".scroll-next");
scrollPrevious = element.locator(".scroll-prev");
scrollView = element.locator(".scroll-view");
await page.goto(fixtureURL("horizontal-scroll--horizontal-scroll"));
await element.evaluate((node: FASTHorizontalScroll) => {
node.speed = 0;
});
});
test.afterAll(async () => {
await page.close();
});
test.describe("Flippers", () => {
test.beforeEach(async () => {
await element.evaluate((node: FASTHorizontalScroll) => {
node.scrollToPosition(0);
});
});
test("should enable the next flipper element when content exceeds horizontal-scroll width", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollNext = element.locator(".scroll-next");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll style="width: 1000px;">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
test("should enable the next flipper element when content exceeds horizontal-scroll width", async () => {
await expect(scrollNext).not.toHaveClass(/disabled/);
});
test("should start in the 0 position", async () => {
test("should start in the 0 position", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await expect(scrollView).toHaveJSProperty("scrollLeft", 0);
});
test("should scroll to the beginning of the last element in full view", async () => {
test("should scroll to the beginning of the last element in full view", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const cards = element.locator("fast-card");
const scrollNext = element.locator(".scroll-next");
const scrollPrevious = element.locator(".scroll-prev");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await element.evaluate((node: FASTHorizontalScroll) => {
node.scrollToNext();
});
@ -62,7 +77,23 @@ test.describe("HorizontalScroll", () => {
await expect(scrollView).toHaveJSProperty("scrollLeft", 375);
});
test("should not scroll past the beginning", async () => {
test("should not scroll past the beginning", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await expect(scrollView).toHaveJSProperty("scrollLeft", 0);
await element.evaluate((node: FASTHorizontalScroll) => {
@ -72,7 +103,27 @@ test.describe("HorizontalScroll", () => {
await expect(scrollView).toHaveJSProperty("scrollLeft", 0);
});
test("should not scroll past the last in view element", async () => {
test("should not scroll past the last in view element", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const cards = element.locator("fast-card");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await (await element.elementHandle())?.waitForElementState("stable");
await element.evaluate((node: FASTHorizontalScroll) => {
const scrollView = node.shadowRoot?.querySelector(".scroll-view");
@ -98,7 +149,31 @@ test.describe("HorizontalScroll", () => {
);
});
test('should set the "disabled" class on the previous flipper when the scroll position is at the beginning of the content', async () => {
test('should set the "disabled" class on the previous flipper when the scroll position is at the beginning of the content', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const cards = element.locator("fast-card");
const scrollNext = element.locator(".scroll-next");
const scrollPrevious = element.locator(".scroll-prev");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await expect(scrollPrevious).toHaveClass(/disabled/);
await element.evaluate((node: FASTHorizontalScroll) => {
@ -114,7 +189,31 @@ test.describe("HorizontalScroll", () => {
await expect(scrollPrevious).toHaveClass(/disabled/);
});
test('should set the "disabled" class on the next flipper when the scroll position is at the end of the content', async () => {
test('should set the "disabled" class on the next flipper when the scroll position is at the end of the content', async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const cards = element.locator("fast-card");
const scrollNext = element.locator(".scroll-next");
const scrollPrevious = element.locator(".scroll-prev");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await expect(scrollNext).not.toHaveClass(/disabled/);
await element.evaluate((node: FASTHorizontalScroll) => {
@ -129,46 +228,60 @@ test.describe("HorizontalScroll", () => {
await expect(scrollNext).not.toHaveClass(/disabled/);
});
});
test("should hide the next flipper if content is less than horizontal-scroll width", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll style="width: 1000px;">
<fast-card style="width: 100px;"></fast-card>
</fast-horizontal-scroll>
`;
test("should hide the next flipper if content is less than horizontal-scroll width", async ({
page,
}) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollNext = element.locator(".scroll-next");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll style="width: 1000px;">
<fast-card style="width: 100px;"></fast-card>
</fast-horizontal-scroll>
`;
});
await expect(scrollNext).toBeHidden();
});
const element = page.locator("fast-horizontal-scroll");
const scrollNext = element.locator(".scroll-next");
await expect(scrollNext).toBeHidden();
});
test.describe("Scrolling", () => {
test("should change scroll stop on resize", async () => {
await page.goto(
fixtureURL("horizontal-scroll--horizontal-scroll", { speed: 0 })
);
test("should change scroll stop on resize", async ({ page }) => {
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
const flippers = element.locator("fast-flipper");
const previousFlipper = flippers.nth(0);
const nextFlipper = flippers.nth(1);
const scrollView = element.locator(".scroll-view");
await nextFlipper.click();
await expect(scrollView).toHaveJSProperty("scrollLeft", 375);
await previousFlipper.click();
await expect(scrollView).toHaveJSProperty("scrollLeft", 0);
await element.evaluate((node: FASTHorizontalScroll) => {
node.style.setProperty("max-width", "400px");
});
@ -181,14 +294,22 @@ test.describe("HorizontalScroll", () => {
test("should scroll to previous when only 2 items wide", async ({ page }) => {
await page.setViewportSize({ width: 250, height: 250 });
await page.goto(
fixtureURL("horizontal-scroll--horizontal-scroll", { speed: 0 })
);
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await element.evaluate((node: FASTHorizontalScroll) => {
node.scrollToNext();
});
@ -205,18 +326,26 @@ test.describe("HorizontalScroll", () => {
test("should scroll item into view when the `scrollInView()` method is invoked", async ({
page,
}) => {
await page.goto(
fixtureURL("horizontal-scroll--horizontal-scroll", { speed: 0 })
);
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollView = element.locator(".scroll-view");
cards = element.locator("fast-card");
const cards = element.locator("fast-card");
const lastCard = cards.last();
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await element.evaluate((node: FASTHorizontalScroll, card) => {
node.scrollInView(card as HTMLElement, 0);
}, await lastCard.elementHandle());
@ -233,9 +362,7 @@ test.describe("HorizontalScroll", () => {
test("Should scroll item into view with `scrollInView()` by index", async ({
page,
}) => {
await page.goto(
fixtureURL("horizontal-scroll--horizontal-scroll", { speed: 0 })
);
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
@ -245,6 +372,16 @@ test.describe("HorizontalScroll", () => {
const lastCard = cards.last();
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await element.evaluate((node: FASTHorizontalScroll, cardsCount) => {
node.scrollInView(cardsCount - 1, 0);
}, await cards.count());
@ -261,9 +398,7 @@ test.describe("HorizontalScroll", () => {
test("Should scroll item into view respecting right and left padding", async ({
page,
}) => {
await page.goto(
fixtureURL("horizontal-scroll--horizontal-scroll", { speed: 0 })
);
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
@ -273,6 +408,16 @@ test.describe("HorizontalScroll", () => {
const thirdLastCard = cards.nth(-4);
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await element.evaluate((node: FASTHorizontalScroll) => {
node.scrollInView(12, 0);
});
@ -307,14 +452,22 @@ test.describe("HorizontalScroll", () => {
test("Should not scroll with `scrollInView()` when the item is in view", async ({
page,
}) => {
await page.goto(
fixtureURL("horizontal-scroll--horizontal-scroll", { speed: 0 })
);
await page.goto("http://localhost:6006");
const element = page.locator("fast-horizontal-scroll");
const scrollView = element.locator(".scroll-view");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-horizontal-scroll speed="0">
${[...Array(16)]
.map((_, i) => /* html */ `<fast-card>card ${i + 1}</fast-card>`)
.join("")}
</fast-horizontal-scroll>
`;
});
await element.evaluate((node: FASTHorizontalScroll) => {
node.scrollInView(2);
});

Просмотреть файл

@ -70,6 +70,14 @@ const styles = css`
contain: layout;
display: block;
position: relative;
max-width: 620px;
margin: 20px;
}
::slotted(fast-card) {
color: var(--neutral-foreground-rest);
height: 200px;
width: 120px;
}
.scroll-view {

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTListboxOption } from "./listbox-option.js";
test.describe("ListboxOption", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should have a role of `option`", async ({ page }) => {
const element = page.locator("fast-option");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-option");
root = page.locator("#root");
await page.goto(fixtureURL("listbox-option--listbox-option"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `option`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option></fast-option>
`;
@ -32,8 +16,12 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveAttribute("role", "option");
});
test("should set the `aria-disabled` attribute when disabled", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute when disabled", async ({ page }) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option disabled></fast-option>
`;
@ -48,8 +36,12 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set the `aria-selected` attribute when selected", async () => {
await root.evaluate(node => {
test("should set the `aria-selected` attribute when selected", async ({ page }) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option selected></fast-option>
`;
@ -64,8 +56,12 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveAttribute("aria-selected", "false");
});
test("should set the `aria-checked` attribute when checked", async () => {
await root.evaluate(node => {
test("should set the `aria-checked` attribute when checked", async ({ page }) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option></fast-option>
`;
@ -86,8 +82,14 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should have an empty string `value` when the `value` attribute exists and is empty", async () => {
await root.evaluate(node => {
test("should have an empty string `value` when the `value` attribute exists and is empty", async ({
page,
}) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option value=""></fast-option>
`;
@ -102,8 +104,14 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveAttribute("value", "");
});
test("should return the text content when the `value` attribute does not exist", async () => {
await root.evaluate(node => {
test("should return the text content when the `value` attribute does not exist", async ({
page,
}) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option>hello</fast-option>
`;
@ -114,8 +122,14 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveJSProperty("value", "hello");
});
test("should return the trimmed text content with the `text` property", async () => {
await root.evaluate(node => {
test("should return the trimmed text content with the `text` property", async ({
page,
}) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option>
hello
@ -129,8 +143,12 @@ test.describe("ListboxOption", () => {
await expect(element).toHaveText("hello world");
});
test("should always return the `value` as a string", async () => {
await root.evaluate(node => {
test("should always return the `value` as a string", async ({ page }) => {
const element = page.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-option value="1"></fast-option>
`;

Просмотреть файл

@ -1,32 +1,15 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTListboxElement } from "./listbox.element.js";
test.describe("Listbox", () => {
let page: Page;
let element: Locator;
let root: Locator;
let options: Locator;
test("should have a tabindex of 0 when `disabled` is not defined", async ({
page,
}) => {
const element = page.locator("fast-listbox");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-listbox");
root = page.locator("#root");
options = element.locator("fast-option");
await page.goto(fixtureURL("listbox--listbox"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a tabindex of 0 when `disabled` is not defined", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -39,8 +22,12 @@ test.describe("Listbox", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should NOT have a tabindex when `disabled` is true", async () => {
await root.evaluate(node => {
test("should NOT have a tabindex when `disabled` is true", async ({ page }) => {
const element = page.locator("fast-listbox");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox disabled></fast-listbox>
`;
@ -49,8 +36,16 @@ test.describe("Listbox", () => {
await expect(element).not.hasAttribute("tabindex");
});
test("should select nothing when no options have the `selected` attribute", async () => {
await root.evaluate(node => {
test("should select nothing when no options have the `selected` attribute", async ({
page,
}) => {
const element = page.locator("fast-listbox");
const options = element.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -71,8 +66,14 @@ test.describe("Listbox", () => {
await expect(element).toHaveJSProperty("selectedIndex", -1);
});
test("should select the option with a `selected` attribute", async () => {
await root.evaluate(node => {
test("should select the option with a `selected` attribute", async ({ page }) => {
const element = page.locator("fast-listbox");
const options = element.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -89,8 +90,14 @@ test.describe("Listbox", () => {
]);
});
test("should set the `size` property to match the `size` attribute", async () => {
await root.evaluate(node => {
test("should set the `size` property to match the `size` attribute", async ({
page,
}) => {
const element = page.locator("fast-listbox");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox size="5"></fast-listbox>
`;
@ -99,8 +106,14 @@ test.describe("Listbox", () => {
await expect(element).toHaveJSProperty("size", 5);
});
test("should set the `size` attribute to match the `size` property", async () => {
await root.evaluate(node => {
test("should set the `size` attribute to match the `size` property", async ({
page,
}) => {
const element = page.locator("fast-listbox");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -117,65 +130,78 @@ test.describe("Listbox", () => {
await expect(element).toHaveAttribute("size", "5");
});
test.describe(
"should set the `size` property to 0 when a negative value is set",
() => {
test("via the `size` property", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
test.describe("should set the `size` property to 0 when a negative value is set", () => {
test("via the `size` property", async ({ page }) => {
const element = page.locator("fast-listbox");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-listbox>
`;
});
await element.evaluate((node: FASTListboxElement) => {
node.size = 1;
});
await expect(element).toHaveJSProperty("size", 1);
await expect(element).toHaveAttribute("size", "1");
await element.evaluate((node: FASTListboxElement) => {
node.size = -1;
});
await expect(element).toHaveJSProperty("size", 0);
await expect(element).toHaveAttribute("size", "0");
});
test("via the `size` attribute", async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
await element.evaluate((node: FASTListboxElement) => {
node.size = 1;
});
await expect(element).toHaveJSProperty("size", 1);
await expect(element).toHaveAttribute("size", "1");
await element.evaluate((node: FASTListboxElement) => {
node.size = -1;
});
await expect(element).toHaveJSProperty("size", 0);
await expect(element).toHaveAttribute("size", "0");
});
test("via the `size` attribute", async ({ page }) => {
const element = page.locator("fast-listbox");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-listbox>
`;
});
await element.evaluate((node: FASTListboxElement) =>
node.setAttribute("size", "1")
);
await expect(element).toHaveJSProperty("size", 1);
await expect(element).toHaveAttribute("size", "1");
await element.evaluate((node: FASTListboxElement) =>
node.setAttribute("size", "-1")
);
await expect(element).toHaveJSProperty("size", 0);
await expect(element).toHaveAttribute("size", "0");
});
}
);
test("should set the `aria-setsize` and `aria-posinset` properties on slotted options", async () => {
await root.evaluate(node => {
await element.evaluate((node: FASTListboxElement) =>
node.setAttribute("size", "1")
);
await expect(element).toHaveJSProperty("size", 1);
await expect(element).toHaveAttribute("size", "1");
await element.evaluate((node: FASTListboxElement) =>
node.setAttribute("size", "-1")
);
await expect(element).toHaveJSProperty("size", 0);
await expect(element).toHaveAttribute("size", "0");
});
});
test("should set the `aria-setsize` and `aria-posinset` properties on slotted options", async ({
page,
}) => {
const element = page.locator("fast-listbox");
const options = element.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -197,8 +223,16 @@ test.describe("Listbox", () => {
}
});
test("should set a unique ID for each slotted option without an ID", async () => {
await root.evaluate(node => {
test("should set a unique ID for each slotted option without an ID", async ({
page,
}) => {
const element = page.locator("fast-listbox");
const options = element.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -215,8 +249,16 @@ test.describe("Listbox", () => {
await expect(options.nth(2)).toHaveAttribute("id", /option-\d+/);
});
test("should set the `aria-activedescendant` property to the ID of the currently selected option", async () => {
await root.evaluate(node => {
test("should set the `aria-activedescendant` property to the ID of the currently selected option", async ({
page,
}) => {
const element = page.locator("fast-listbox");
const options = element.locator("fast-option");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>
@ -239,8 +281,14 @@ test.describe("Listbox", () => {
}
});
test("should set the `aria-multiselectable` attribute to match the `multiple` attribute", async () => {
await root.evaluate(node => {
test("should set the `aria-multiselectable` attribute to match the `multiple` attribute", async ({
page,
}) => {
const element = page.locator("fast-listbox");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-listbox>
<fast-option>Option 1</fast-option>

Просмотреть файл

@ -1,7 +1,3 @@
export { FASTMenuItem, MenuItemRole, roleForMenuItem } from "./menu-item.js";
export type { MenuItemOptions } from "./menu-item.js";
export { menuItemTemplate } from "./menu-item.template.js";
export {
FASTMenuItem,
MenuItemOptions,
MenuItemRole,
roleForMenuItem,
} from "./menu-item.js";

Просмотреть файл

@ -1,30 +1,16 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTMenuItem } from "./menu-item.js";
import { MenuItemRole } from "./menu-item.options.js";
test.describe("Menu item", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should include a role of `menuitem` by default when no role is provided", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-menu-item");
root = page.locator("#root");
await page.goto(fixtureURL("menu-item--menu-item"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of `menuitem` by default when no role is provided", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item>Menu item</fast-menu-item>
`;
@ -33,30 +19,37 @@ test.describe("Menu item", () => {
await expect(element).toHaveAttribute("role", MenuItemRole.menuitem);
});
test.describe(
"should include a matching role when the `role` property is provided",
() => {
let role: MenuItemRole;
for (role in MenuItemRole) {
test(role, async () => {
await root.evaluate(node => {
node.innerHTML = /* html */ `
test.describe("should include a matching role when the `role` property is provided", () => {
let role: MenuItemRole;
for (role in MenuItemRole) {
test(role, async ({ page }) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item>Menu item</fast-menu-item>
`;
});
await element.evaluate(
(node: FASTMenuItem, role) => (node.role = role),
role
);
await expect(element).toHaveAttribute("role", role);
});
}
}
);
test("should set the `aria-disabled` attribute with the `disabled` value when provided", async () => {
await root.evaluate(node => {
await element.evaluate((node: FASTMenuItem, role) => {
node.role = role;
}, role);
await expect(element).toHaveAttribute("role", role);
});
}
});
test("should set the `aria-disabled` attribute with the `disabled` value when provided", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item disabled>Menu item</fast-menu-item>
`;
@ -65,8 +58,14 @@ test.describe("Menu item", () => {
await expect(element).toHaveAttribute("aria-disabled", "true");
});
test("should set an `aria-expanded` attribute with the `expanded` value when provided", async () => {
await root.evaluate(node => {
test("should set an `aria-expanded` attribute with the `expanded` value when provided", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item expanded>Menu item</fast-menu-item>
`;
@ -75,8 +74,14 @@ test.describe("Menu item", () => {
await expect(element).toHaveAttribute("aria-expanded", "true");
});
test("should set an `aria-checked` attribute with the `checked` value when provided to a menuitemcheckbox", async () => {
await root.evaluate(node => {
test("should set an `aria-checked` attribute with the `checked` value when provided to a menuitemcheckbox", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item role="menuitemcheckbox" checked>Menu item</fast-menu-item>
`;
@ -85,8 +90,14 @@ test.describe("Menu item", () => {
await expect(element).toHaveAttribute("aria-checked", "true");
});
test("should NOT set an `aria-checked` attribute when checked is provided to a menuitem", async () => {
await root.evaluate(node => {
test("should NOT set an `aria-checked` attribute when checked is provided to a menuitem", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item role="menuitem" checked>Menu item</fast-menu-item>
`;
@ -95,14 +106,20 @@ test.describe("Menu item", () => {
await expect(element).not.toHaveAttribute("aria-checked", "true");
});
test("should toggle the `aria-checked` attribute of checkbox item when clicked", async () => {
await root.evaluate(node => {
test("should toggle the `aria-checked` attribute of checkbox item when clicked", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item role="menuitemcheckbox">Menu item</fast-menu-item>
`;
});
await expect(element).hasAttribute("aria-checked", "false");
await expect(element).toHaveAttribute("aria-checked", "false");
await element.click();
@ -113,14 +130,20 @@ test.describe("Menu item", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should set the `aria-checked` attribute of radio item to true when clicked", async () => {
await root.evaluate(node => {
test("should set the `aria-checked` attribute of radio item when clicked", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item role="menuitemradio">Menu item</fast-menu-item>
`;
});
await expect(element).hasAttribute("aria-checked", "false");
await expect(element).toHaveAttribute("aria-checked", "false");
await element.click();
@ -132,8 +155,12 @@ test.describe("Menu item", () => {
});
test.describe("events", () => {
test("should emit a `change` an event when clicked", async () => {
await root.evaluate(node => {
test("should emit a `change` event when clicked", async ({ page }) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item>Menu item</fast-menu-item>
`;
@ -149,11 +176,17 @@ test.describe("Menu item", () => {
element.click(),
]);
expect(wasClicked).toBe(true);
expect.soft(wasClicked).toBe(true);
});
test("should emit a `keydown` event when space key is invoked", async () => {
await root.evaluate(node => {
test("should emit a `keydown` event when space key is invoked", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item>Menu item</fast-menu-item>
`;
@ -165,20 +198,30 @@ test.describe("Menu item", () => {
new Promise(resolve => {
node.addEventListener("keydown", () => resolve(true));
}),
new Promise(resolve => setTimeout(() => resolve(false), 100)),
new Promise(resolve =>
requestAnimationFrame(() =>
setTimeout(() => resolve(false), 100)
)
),
])
),
// FIXME: Playwright's keyboard API is not working as expected.
element.evaluate(node =>
node.dispatchEvent(new KeyboardEvent("keydown", { key: " " }))
),
element.evaluate(node => {
node.dispatchEvent(new KeyboardEvent("keydown", { key: " " }));
}),
]);
expect(wasChanged).toBe(true);
expect.soft(wasChanged).toBe(true);
});
test("should emit a `keydown` event when `Enter` key is invoked", async () => {
await root.evaluate(node => {
test("should emit a `keydown` event when `Enter` key is invoked", async ({
page,
}) => {
const element = page.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu-item>Menu item</fast-menu-item>
`;
@ -190,16 +233,20 @@ test.describe("Menu item", () => {
new Promise(resolve => {
node.addEventListener("keydown", () => resolve(true));
}),
new Promise(resolve => setTimeout(() => resolve(false), 100)),
new Promise(resolve =>
requestAnimationFrame(() =>
setTimeout(() => resolve(false), 100)
)
),
])
),
// FIXME: Playwright's keyboard API is not working as expected.
element.evaluate(node =>
node.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }))
),
element.evaluate(node => {
node.dispatchEvent(new KeyboardEvent("keydown", { key: "Enter" }));
}),
]);
expect(wasChanged).toBe(true);
expect.soft(wasChanged).toBe(true);
});
});
});

Просмотреть файл

@ -1,4 +1,4 @@
import type { Placement } from "@floating-ui/dom";
import type { ElementRects, Placement } from "@floating-ui/dom";
import { autoUpdate, computePosition, flip, shift, size } from "@floating-ui/dom";
import { attr, FASTElement, observable, Updates } from "@microsoft/fast-element";
import {
@ -8,10 +8,10 @@ import {
keyEscape,
keySpace,
} from "@microsoft/fast-web-utilities";
import type { StaticallyComposableHTML } from "../utilities/template-helpers.js";
import type { StartEndOptions } from "../patterns/start-end.js";
import { StartEnd } from "../patterns/start-end.js";
import { applyMixins } from "../utilities/apply-mixins.js";
import type { StaticallyComposableHTML } from "../utilities/template-helpers.js";
import { MenuItemRole, roleForMenuItem } from "./menu-item.options.js";
export { MenuItemRole, roleForMenuItem };
@ -337,7 +337,13 @@ export class FASTMenuItem extends FASTElement {
middleware: [
shift(),
size({
apply: ({ availableWidth, rects }) => {
apply: ({
availableWidth,
rects,
}: {
availableWidth: number;
rects: ElementRects;
}) => {
if (availableWidth < rects.floating.width) {
fallbackPlacements.push("bottom-end", "top-end");
}

Просмотреть файл

@ -1,32 +1,12 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTMenu } from "./menu.js";
test.describe("Menu", () => {
let page: Page;
let element: Locator;
let root: Locator;
let menuItems: Locator;
test("should have a role of `menu`", async ({ page }) => {
const element = page.locator("fast-menu");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-menu");
root = page.locator("#root");
menuItems = element.locator("fast-menu-item");
await page.goto(fixtureURL("menu--menu"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `menu`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item</fast-menu-item>
@ -37,8 +17,16 @@ test.describe("Menu", () => {
await expect(element).toHaveAttribute("role", "menu");
});
test("should set `tabindex` of the first focusable menu item to 0", async () => {
await root.evaluate(node => {
test("should set `tabindex` of the first focusable menu item to 0", async ({
page,
}) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item</fast-menu-item>
@ -50,8 +38,12 @@ test.describe("Menu", () => {
await expect(menuItems.first()).toHaveAttribute("tabindex", "0");
});
test("should NOT set any `tabindex` on non-menu-item elements", async () => {
await root.evaluate(node => {
test("should NOT set any `tabindex` on non-menu-item elements", async ({ page }) => {
const element = page.locator("fast-menu");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item</fast-menu-item>
@ -65,8 +57,14 @@ test.describe("Menu", () => {
expect(await divider.getAttribute("tabindex")).toBeNull();
});
test("should focus on first menu item when focus is called", async () => {
await root.evaluate(node => {
test("should focus on first menu item when focus is called", async ({ page }) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item</fast-menu-item>
@ -75,37 +73,43 @@ test.describe("Menu", () => {
`;
});
await element.waitFor({ state: "attached" });
await expect(menuItems.first()).toHaveAttribute("tabindex", "0");
await root.evaluate(node => {
document.querySelector<FASTMenu>("fast-menu")?.focus();
await element.evaluate(node => {
node.focus();
});
expect(
await menuItems.first().evaluate(node => {
return node.isSameNode(document.activeElement);
})
).toBeTruthy();
await expect(menuItems.first()).toBeFocused();
});
test("should not throw when focus is called with no items", async () => {
await root.evaluate(node => {
test("should not throw when focus is called with no items", async ({ page }) => {
const element = page.locator("fast-menu");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu></fast-menu>
`;
});
await root.evaluate(node => {
document.querySelector<FASTMenu>("fast-menu")?.focus();
await element.evaluate(node => {
node.focus();
});
expect(await page.evaluate(() => document.activeElement?.id)).toBe("");
await expect(element).not.toBeFocused();
// expect(await page.evaluate(() => document.activeElement?.id)).toBe("");
});
test("should not throw when focus is called before initialization is complete", async () => {
await root.evaluate(node => {
test("should not throw when focus is called before initialization is complete", async ({
page,
}) => {
const element = page.locator("fast-menu");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const menu = document.createElement("fast-menu");
@ -115,11 +119,19 @@ test.describe("Menu", () => {
node.append(menu);
});
expect(await page.evaluate(() => document.activeElement?.id)).toBe("");
await expect(element).not.toBeFocused();
});
test("should focus disabled items", async () => {
await root.evaluate(node => {
test("should focus disabled items", async ({ page }) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
const firstMenuItem = menuItems.first();
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item disabled>Menu item</fast-menu-item>
@ -128,8 +140,6 @@ test.describe("Menu", () => {
`;
});
const firstMenuItem = menuItems.first();
await expect(firstMenuItem).toBeDisabled();
await expect(firstMenuItem).toHaveAttribute("tabindex", "0");
@ -140,26 +150,40 @@ test.describe("Menu", () => {
});
["menuitem", "menuitemcheckbox", "menuitemradio"].forEach(role => {
test(`should accept elements as focusable child with "${role}" role`, async () => {
await root.evaluate(
test(`should accept elements as focusable child with "${role}" role`, async ({
page,
}) => {
const element = page.locator("fast-menu");
const compatibleElement = element.locator(`[role="${role}"]`);
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { role }) => {
node.innerHTML = /* html */ `
<fast-menu>
<div role="${role}">Menu item</div>
</fast-menu>
`;
<fast-menu>
<div role="${role}">Menu item</div>
</fast-menu>
`;
},
{ role }
);
await expect(
page.locator(`fast-menu [role="${role}"]`).first()
).toHaveAttribute("tabindex", "0");
await expect(compatibleElement).toHaveAttribute("tabindex", "0");
});
});
test("should not navigate to hidden items when changed after connection", async () => {
await root.evaluate(node => {
test("should not navigate to hidden items when changed after connection", async ({
page,
}) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item 1</fast-menu-item>
@ -217,8 +241,16 @@ test.describe("Menu", () => {
await expect(menuItems.nth(2)).toBeFocused();
});
test("should treat all checkbox menu items as individually selectable items", async () => {
await root.evaluate(node => {
test("should treat all checkbox menu items as individually selectable items", async ({
page,
}) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item role="menuitemcheckbox">Menu item 1</fast-menu-item>
@ -246,8 +278,16 @@ test.describe("Menu", () => {
}
});
test(`should treat all radio menu items as a radiogroup and limit selection to one item within the group`, async () => {
await root.evaluate(node => {
test(`should treat all radio menu items as a radiogroup and limit selection to one item within the group`, async ({
page,
}) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item role="menuitemradio">Menu item 1</fast-menu-item>
@ -282,8 +322,16 @@ test.describe("Menu", () => {
await expect(menuItems.nth(2)).toHaveAttribute("aria-checked", "true");
});
test('should use elements with `[role="separator"]` to divide radio menu items into different radio groups', async () => {
await root.evaluate(node => {
test('should use elements with `[role="separator"]` to divide radio menu items into different radio groups', async ({
page,
}) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item role="menuitemradio">Menu item 1</fast-menu-item>
@ -324,8 +372,14 @@ test.describe("Menu", () => {
await expect(menuItems.nth(3)).toHaveAttribute("aria-checked", "true");
});
test("should navigate the menu on arrow up/down keys", async () => {
await root.evaluate(node => {
test("should navigate the menu on arrow up/down keys", async ({ page }) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item 1</fast-menu-item>
@ -359,9 +413,15 @@ test.describe("Menu", () => {
await expect(menuItems.nth(3)).toBeFocused();
});
test("should close the menu when pressing the escape key", async () => {
test("should close the menu when pressing the escape key", async ({ page }) => {
test.slow();
await root.evaluate(node => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item 1
@ -392,8 +452,16 @@ test.describe("Menu", () => {
await expect(menuItems.first()).toBeFocused();
});
test("should not navigate to hidden items when set before connection", async () => {
await root.evaluate(node => {
test("should not navigate to hidden items when set before connection", async ({
page,
}) => {
const element = page.locator("fast-menu");
const menuItems = element.locator("fast-menu-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-menu>
<fast-menu-item>Menu item 1</fast-menu-item>

Просмотреть файл

@ -1,6 +1,4 @@
import { html } from "@microsoft/fast-element";
import { attr } from "@microsoft/fast-element";
import { css } from "@microsoft/fast-element";
import { attr, css, html } from "@microsoft/fast-element";
import { FASTMenuItem, MenuItemRole, menuItemTemplate } from "../../menu-item/index.js";
import { FASTMenu } from "../menu.js";
import { menuTemplate } from "../menu.template.js";
@ -57,8 +55,8 @@ export class FancyMenu extends FASTMenu {
this.menuItems
?.filter(this.isMenuItemElement)
.forEach((item: HTMLElement, index: number) => {
const indent: FancyMenuItemColumnCount = this.menuItems!.reduce(
.forEach((item: HTMLElement, index: number, menuItems) => {
const indent: FancyMenuItemColumnCount = menuItems.reduce(
(accum, current) => {
const elementValue = FancyMenu.elementIndent(
current as HTMLElement

Просмотреть файл

@ -5,10 +5,7 @@ import { storyTemplate as menuItemStoryTemplate } from "../../menu-item/stories/
import type { Meta, Story, StoryArgs } from "../../__test__/helpers.js";
import { renderComponent } from "../../__test__/helpers.js";
import type { FASTMenu } from "../menu.js";
import type {
FancyMenu as MyFancyMenu,
FancyMenuItem as MyFancyMenuItem,
} from "./menu.register.js";
import type { FancyMenu as MyFancyMenu } from "./menu.register.js";
const storyTemplate = html<StoryArgs<FASTMenu>>`
<fast-menu slot="${x => x.slot}">${x => x.storyContent}</fast-menu>
@ -39,15 +36,6 @@ const fancyMenuItemStoryTemplate = html<StoryArgs<FASTMenuItem>>`
</fancy-menu-item>
`;
const fancyStoryContentTemplate = html`
${repeat(
x => x.storyItems,
html<StoryArgs<MyFancyMenuItem>>`
${x => x.template ?? fancyMenuItemStoryTemplate}
`
)}
`;
export default {
title: "Menu",
args: {

Просмотреть файл

@ -1,33 +1,16 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTNumberField } from "./number-field.js";
test.describe("NumberField", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
test("should initialize to the provided value attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-number-field");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-number-field");
root = page.locator("#root");
control = element.locator(".control");
await page.goto(fixtureURL("number-field--number-field"));
});
test.afterAll(async () => {
await page.close();
});
test("should initialize to the provided value attribute if set pre-connection", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field value="10"></fast-number-field>
`;
@ -36,8 +19,16 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "10");
});
test("should set the `autofocus` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `autofocus` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field autofocus></fast-number-field>
`;
@ -46,8 +37,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveBooleanAttribute("autofocus");
});
test("should set the `disabled` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `disabled` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field disabled></fast-number-field>
`;
@ -55,8 +54,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveBooleanAttribute("disabled");
});
test("should set the `readonly` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `readonly` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field readonly></fast-number-field>
`;
@ -64,8 +71,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveBooleanAttribute("readonly");
});
test("should set the `required` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `required` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field required></fast-number-field>
`;
@ -103,8 +118,16 @@ test.describe("NumberField", () => {
})) {
const attrToken = spinalCase(attribute);
test(`should set the \`${attrToken}\` attribute to "${value}" on the internal control`, async () => {
await root.evaluate(
test(`should set the \`${attrToken}\` attribute to "${value}" on the internal control`, async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { attrToken, value }) => {
node.innerHTML = /* html */ `
<fast-number-field ${attrToken}="${value}"></fast-number-field>
@ -117,8 +140,16 @@ test.describe("NumberField", () => {
});
}
test("should set `value` property equal to the `max` property when value is greater than max", async () => {
await root.evaluate(node => {
test("should set `value` property equal to the `max` property when value is greater than max", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field max="10"></fast-number-field>
`;
@ -129,8 +160,14 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "10");
});
test("should set `value` property equal to the `max` property when max is less than the value", async () => {
await root.evaluate(node => {
test("should set `value` property equal to the `max` property when max is less than the value", async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field value="20"></fast-number-field>
`;
@ -145,8 +182,16 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "10");
});
test("should set the `value` property equal to the `min` property when value is less than min", async () => {
await root.evaluate(node => {
test("should set the `value` property equal to the `min` property when value is less than min", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="10"></fast-number-field>
`;
@ -159,8 +204,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("10");
});
test("should update the `value` property when the `min` property is greater than the `value`", async () => {
await root.evaluate(node => {
test("should update the `value` property when the `min` property is greater than the `value`", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field value="10"></fast-number-field>
`;
@ -175,8 +228,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("20");
});
test("should set the `max` property equal to the `min` property when min is greater than max", async () => {
await root.evaluate(node => {
test("should set the `max` property equal to the `min` property when min is greater than max", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="10" max="5"></fast-number-field>
`;
@ -187,8 +248,14 @@ test.describe("NumberField", () => {
await expect(control).toHaveJSProperty("max", "10");
});
test("should initialize to the provided `value` attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -201,8 +268,16 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "10");
});
test('should fire a "change" event when the internal control emits a "change" event', async () => {
await root.evaluate(node => {
test('should fire a "change" event when the internal control emits a "change" event', async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -225,8 +300,14 @@ test.describe("NumberField", () => {
expect(wasChanged).toBeTruthy();
});
test('should fire an "input" event when incrementing or decrementing', async () => {
await root.evaluate(node => {
test('should fire an "input" event when incrementing or decrementing', async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -261,8 +342,14 @@ test.describe("NumberField", () => {
expect(wasDecreased).toBeTruthy();
});
test("should allow positive float numbers", async () => {
await root.evaluate(node => {
test("should allow positive float numbers", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -275,8 +362,14 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("1.1");
});
test("should allow negative float numbers", async () => {
await root.evaluate(node => {
test("should allow negative float numbers", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -289,8 +382,14 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("-1.1");
});
test("should allow positive integer numbers", async () => {
await root.evaluate(node => {
test("should allow positive integer numbers", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -303,8 +402,14 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("1");
});
test("should allow negative integer numbers", async () => {
await root.evaluate(node => {
test("should allow negative integer numbers", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -319,8 +424,14 @@ test.describe("NumberField", () => {
// TODO: This test doesn't account for the `e` character.
// See https://github.com/microsoft/fast/issues/6251
test("should disallow non-numeric characters", async () => {
await root.evaluate(node => {
test("should disallow non-numeric characters", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -333,8 +444,14 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("");
});
test('should set the `step` property to "1" by default', async () => {
await root.evaluate(node => {
test('should set the `step` property to "1" by default', async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -343,8 +460,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveAttribute("step", "1");
});
test("should update the `step` attribute on the internal control when the `step` property is changed", async () => {
await root.evaluate(node => {
test("should update the `step` attribute on the internal control when the `step` property is changed", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -357,8 +482,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveAttribute("step", "2");
});
test("should increment the `value` property by the step amount when the `stepUp()` method is invoked", async () => {
await root.evaluate(node => {
test("should increment the `value` property by the step amount when the `stepUp()` method is invoked", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field step="2" value="5"></fast-number-field>
`;
@ -373,8 +506,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("7");
});
test("should decrement the `value` property by the step amount when the `stepDown()` method is invoked", async () => {
await root.evaluate(node => {
test("should decrement the `value` property by the step amount when the `stepDown()` method is invoked", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field step="2" value="5"></fast-number-field>
`;
@ -389,8 +530,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("3");
});
test("should offset an undefined `value` from zero when stepped down", async () => {
await root.evaluate(node => {
test("should offset an undefined `value` from zero when stepped down", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field step="2"></fast-number-field>
`;
@ -405,8 +554,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("-2");
});
test("should offset an undefined `value` from zero when stepped up", async () => {
await root.evaluate(node => {
test("should offset an undefined `value` from zero when stepped up", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field step="2"></fast-number-field>
`;
@ -421,8 +578,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("2");
});
test("should offset the `value` from zero after stepping down when `min` is a negative value", async () => {
await root.evaluate(node => {
test("should offset the `value` from zero after stepping down when `min` is a negative value", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="-10"></fast-number-field>
`;
@ -437,8 +602,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("0");
});
test("should offset the `value` from zero after stepping up when `min` is a negative value", async () => {
await root.evaluate(node => {
test("should offset the `value` from zero after stepping up when `min` is a negative value", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="-10"></fast-number-field>
`;
@ -453,8 +626,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("0");
});
test("should set `value` to match `min` after stepping down when `min` is greater than 0", async () => {
await root.evaluate(node => {
test("should set `value` to match `min` after stepping down when `min` is greater than 0", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="10"></fast-number-field>
`;
@ -469,8 +650,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("10");
});
test("should set `value` to match `min` after stepping up when `min` is greater than 0", async () => {
await root.evaluate(node => {
test("should set `value` to match `min` after stepping up when `min` is greater than 0", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="10"></fast-number-field>
`;
@ -485,8 +674,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("10");
});
test("should set the `value` to match `max` after stepping down when `value` is undefined and `min` and `max` are less than zero", async () => {
await root.evaluate(node => {
test("should set the `value` to match `max` after stepping down when `value` is undefined and `min` and `max` are less than zero", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="-10" max="-5"></fast-number-field>
`;
@ -501,8 +698,16 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("-5");
});
test("should set the `value` to match `max` after stepping up when `value` is undefined and `min` and `max` are less than zero", async () => {
await root.evaluate(node => {
test("should set the `value` to match `max` after stepping up when `value` is undefined and `min` and `max` are less than zero", async ({
page,
}) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field min="-10" max="-5"></fast-number-field>
`;
@ -517,8 +722,14 @@ test.describe("NumberField", () => {
await expect(control).toHaveValue("-5");
});
test("should update the proxy value when stepping down", async () => {
await root.evaluate(node => {
test("should update the proxy value when stepping down", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field step="2" value="5"></fast-number-field>
`;
@ -537,8 +748,14 @@ test.describe("NumberField", () => {
);
});
test("should update the proxy value when stepping up", async () => {
await root.evaluate(node => {
test("should update the proxy value when stepping up", async ({ page }) => {
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field step="2" value="5"></fast-number-field>
`;
@ -557,11 +774,17 @@ test.describe("NumberField", () => {
);
});
test("should correct rounding errors when stepping down", async () => {
test("should correct rounding errors when stepping down", async ({ page }) => {
const step = 0.1;
const value = 0.2;
await root.evaluate(
const element = page.locator("fast-number-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { step, value }) => {
node.innerHTML = /* html */ `
<fast-number-field step="${step}" value="${value}"></fast-number-field>
@ -581,8 +804,14 @@ test.describe("NumberField", () => {
}
});
test("should not render step controls when `hide-step` attribute is present", async () => {
await root.evaluate(node => {
test("should not render step controls when `hide-step` attribute is present", async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field hide-step></fast-number-field>
`;
@ -593,8 +822,14 @@ test.describe("NumberField", () => {
await expect(controls).toBeHidden();
});
test("should not render step controls when `readonly` attribute is present", async () => {
await root.evaluate(node => {
test("should not render step controls when `readonly` attribute is present", async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field readonly></fast-number-field>
`;
@ -605,8 +840,14 @@ test.describe("NumberField", () => {
await expect(controls).toHaveCount(0);
});
test("should allow setting `valueAsNumber` property with a number", async () => {
await root.evaluate(node => {
test("should allow setting `valueAsNumber` property with a number", async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field></fast-number-field>
`;
@ -619,8 +860,14 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "18");
});
test("should allow reading the `valueAsNumber` property as number", async () => {
await root.evaluate(node => {
test("should allow reading the `valueAsNumber` property as number", async ({
page,
}) => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-number-field value="18"></fast-number-field>
`;
@ -630,10 +877,16 @@ test.describe("NumberField", () => {
});
test.describe("when the owning form's reset() method is invoked", () => {
test('should reset its `value` property to "" if no `value` attribute is set', async () => {
test('should reset its `value` property to "" if no `value` attribute is set', async ({
page,
}) => {
const form = page.locator("form");
await root.evaluate(node => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-number-field></fast-number-field>
@ -652,10 +905,16 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "");
});
test("should reset the `value` property to match the `value` attribute", async () => {
test("should reset the `value` property to match the `value` attribute", async ({
page,
}) => {
const form = page.locator("form");
await root.evaluate(node => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-number-field value="10"></fast-number-field>
@ -676,10 +935,16 @@ test.describe("NumberField", () => {
await expect(element).toHaveJSProperty("value", "10");
});
test("should put the control into a clean state, where `value` attribute modifications change the `value` property prior to user or programmatic interaction", async () => {
test("should put the control into a clean state, where `value` attribute modifications change the `value` property prior to user or programmatic interaction", async ({
page,
}) => {
const form = page.locator("form");
await root.evaluate(node => {
const element = page.locator("fast-number-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-number-field value="10"></fast-number-field>

Просмотреть файл

@ -1,10 +1,5 @@
export { tagFor, TemplateElementDependency } from "./tag-for.js";
export { tagFor } from "../patterns/tag-for.js";
export type { TemplateElementDependency } from "../patterns/tag-for.js";
export { ARIAGlobalStatesAndProperties } from "./aria-global.js";
export {
EndOptions,
endSlotTemplate,
StartEnd,
StartEndOptions,
StartOptions,
startSlotTemplate,
} from "./start-end.js";
export { endSlotTemplate, StartEnd, startSlotTemplate } from "./start-end.js";
export type { EndOptions, StartEndOptions, StartOptions } from "./start-end.js";

Просмотреть файл

@ -5,7 +5,8 @@ import {
ViewTemplate,
when,
} from "@microsoft/fast-element";
import { tagFor, TemplateElementDependency } from "../patterns/tag-for.js";
import type { TemplateElementDependency } from "../patterns/tag-for.js";
import { tagFor } from "../patterns/tag-for.js";
import type { FASTPicker } from "./picker.js";
function defaultListItemTemplate(options: PickerOptions): ViewTemplate {

Просмотреть файл

@ -1,28 +1,12 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
test.describe("Progress ring", () => {
let page: Page;
let element: Locator;
let root: Locator;
test.describe("Progress Ring", () => {
test("should include a role of progressbar", async ({ page }) => {
const element = page.locator("fast-progress-ring");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-progress-ring");
root = page.locator("#root");
await page.goto(fixtureURL("progress-progress-ring--progress-ring"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of progressbar", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress-ring></fast-progress-ring>
`;
@ -31,8 +15,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("role", "progressbar");
});
test("should set the `aria-valuenow` attribute with the `value` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuenow` attribute with the `value` property when provided", async ({
page,
}) => {
const element = page.locator("fast-progress-ring");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress-ring value="50"></fast-progress-ring>
`;
@ -41,8 +31,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("aria-valuenow", "50");
});
test("should set the `aria-valuemin` attribute with the `min` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuemin` attribute with the `min` property when provided", async ({
page,
}) => {
const element = page.locator("fast-progress-ring");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress-ring min="10"></fast-progress-ring>
`;
@ -51,8 +47,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("aria-valuemin", "10");
});
test("should set the `aria-valuemax` attribute with the `max` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuemax` attribute with the `max` property when provided", async ({
page,
}) => {
const element = page.locator("fast-progress-ring");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress-ring max="75"></fast-progress-ring>
`;
@ -61,8 +63,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("aria-valuemax", "75");
});
test("should render an element with a `determinate` slot when a value is provided", async () => {
await root.evaluate(node => {
test("should render an element with a `determinate` slot when a value is provided", async ({
page,
}) => {
const element = page.locator("fast-progress-ring");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress-ring value="50"></fast-progress-ring>
`;

Просмотреть файл

@ -1,33 +1,29 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTProgress } from "./progress.js";
test.describe("Progress ring", () => {
let page: Page;
let element: Locator;
let root: Locator;
test.describe("Progress", () => {
test("should include a role of progressbar", async ({ page }) => {
const element = page.locator("fast-progress");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-progress");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress></fast-progress>
`;
});
root = page.locator("#root");
await page.goto(fixtureURL("progress--progress"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of progressbar", async () => {
await expect(element).toHaveAttribute("role", "progressbar");
});
test("should set the `aria-valuenow` attribute with the `value` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuenow` attribute with the `value` property when provided", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress value="50"></fast-progress>
`;
@ -36,8 +32,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("aria-valuenow", "50");
});
test("should set the `aria-valuemin` attribute with the `min` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuemin` attribute with the `min` property when provided", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress min="50"></fast-progress>
`;
@ -46,8 +48,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("aria-valuemin", "50");
});
test("should set the `aria-valuemax` attribute with the `max` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuemax` attribute with the `max` property when provided", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress max="50"></fast-progress>
`;
@ -56,8 +64,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveAttribute("aria-valuemax", "50");
});
test("should render an element with a `determinate` slot when a value is provided", async () => {
await root.evaluate(node => {
test("should render an element with a `determinate` slot when a value is provided", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress value="50"></fast-progress>
`;
@ -68,8 +82,14 @@ test.describe("Progress ring", () => {
await expect(progress).toHaveAttribute("slot", "determinate");
});
test("should render an element with an `indeterminate` slot when no value is provided", async () => {
await root.evaluate(node => {
test("should render an element with an `indeterminate` slot when no value is provided", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress></fast-progress>
`;
@ -80,8 +100,14 @@ test.describe("Progress ring", () => {
await expect(progress).toHaveAttribute("slot", "indeterminate");
});
test("should return the `percentComplete` property as a value between 0 and 100 when `min` and `max` are unset", async () => {
await root.evaluate(node => {
test("should return the `percentComplete` property as a value between 0 and 100 when `min` and `max` are unset", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress value="50"></fast-progress>
`;
@ -90,8 +116,14 @@ test.describe("Progress ring", () => {
await expect(element).toHaveJSProperty("percentComplete", 50);
});
test("should set the `percentComplete` property to match the current `value` in the range of `min` and `max`", async () => {
await root.evaluate(node => {
test("should set the `percentComplete` property to match the current `value` in the range of `min` and `max`", async ({
page,
}) => {
const element = page.locator("fast-progress");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-progress value="0"></fast-progress>
`;

Просмотреть файл

@ -1,34 +1,16 @@
import type { Locator } from "@playwright/test";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import type { FASTRadio } from "../radio/index.js";
import { fixtureURL } from "../__test__/helpers.js";
import { RadioGroupOrientation } from "./radio-group.options.js";
import type { FASTRadioGroup } from "./radio-group.js";
import { RadioGroupOrientation } from "./radio-group.options.js";
test.describe("Radio Group", () => {
let page: Page;
let element: Locator;
let root: Locator;
let radios: Locator;
test("should have a role of `radiogroup`", async ({ page }) => {
const element = page.locator("fast-radio-group");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-radio-group");
root = page.locator("#root");
radios = element.locator("fast-radio");
await page.goto(fixtureURL("radio-group--radio-group"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `radiogroup`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -37,8 +19,14 @@ test.describe("Radio Group", () => {
await expect(element).toHaveAttribute("role", "radiogroup");
});
test("should set a default `aria-orientation` value when `orientation` is not defined", async () => {
await root.evaluate(node => {
test("should set a default `aria-orientation` value when `orientation` is not defined", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -50,8 +38,14 @@ test.describe("Radio Group", () => {
);
});
test("should set a matching class on the `positioning-region` when an orientation is provided", async () => {
await root.evaluate(node => {
test("should set a matching class on the `positioning-region` when an orientation is provided", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -75,8 +69,14 @@ test.describe("Radio Group", () => {
await expect(positioningRegion).toHaveClass(/horizontal/);
});
test("should set the `aria-orientation` attribute equal to the `orientation` value", async () => {
await root.evaluate(node => {
test("should set the `aria-orientation` attribute equal to the `orientation` value", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -101,8 +101,12 @@ test.describe("Radio Group", () => {
);
});
test("should set the `aria-disabled` attribute when disabled", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute when disabled", async ({ page }) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group disabled></fast-radio-group>
`;
@ -111,8 +115,14 @@ test.describe("Radio Group", () => {
await expect(element).toHaveAttribute("aria-disabled", "true");
});
test("should set the `aria-disabled` attribute equal to the `disabled` property", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to the `disabled` property", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -133,8 +143,14 @@ test.describe("Radio Group", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set the `aria-readonly` attribute when the `readonly` attribute is present", async () => {
await root.evaluate(node => {
test("should set the `aria-readonly` attribute when the `readonly` attribute is present", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group readonly></fast-radio-group>
`;
@ -143,8 +159,14 @@ test.describe("Radio Group", () => {
await expect(element).toHaveAttribute("aria-readonly", "true");
});
test("should set the `aria-readonly` attribute equal to the `readonly` property", async () => {
await root.evaluate(node => {
test("should set the `aria-readonly` attribute equal to the `readonly` property", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -165,8 +187,14 @@ test.describe("Radio Group", () => {
await expect(element).toHaveAttribute("aria-readonly", "false");
});
test("should NOT set a default `aria-disabled` value when `disabled` is not defined", async () => {
await root.evaluate(node => {
test("should NOT set a default `aria-disabled` value when `disabled` is not defined", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group></fast-radio-group>
`;
@ -175,8 +203,16 @@ test.describe("Radio Group", () => {
await expect(element).not.hasAttribute("aria-disabled");
});
test("should NOT modify child radio elements disabled state when the `disabled` attribute is present", async () => {
await root.evaluate(node => {
test("should NOT modify child radio elements disabled state when the `disabled` attribute is present", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group>
<fast-radio></fast-radio>
@ -243,11 +279,15 @@ test.describe("Radio Group", () => {
).toEqual(expectedThird);
});
test("should NOT be focusable when disabled", async () => {
test("should NOT be focusable when disabled", async ({ page }) => {
const first: Locator = page.locator("button", { hasText: "First" });
const second: Locator = page.locator("button", { hasText: "Second" });
await root.evaluate(node => {
const element = page.locator("fast-radio-group");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<button>First</button>
<fast-radio-group disabled>
@ -276,8 +316,14 @@ test.describe("Radio Group", () => {
).toBeTruthy();
});
test("should NOT be focusable via click when disabled", async () => {
await root.evaluate(node => {
test("should NOT be focusable via click when disabled", async ({ page }) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<button>Button</button>
<fast-radio-group>
@ -317,8 +363,16 @@ test.describe("Radio Group", () => {
}
});
test("should set tabindex of 0 to a child radio with a matching `value`", async () => {
await root.evaluate(node => {
test("should set tabindex of 0 to a child radio with a matching `value`", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group value="foo">
<fast-radio value="foo"></fast-radio>
@ -331,8 +385,16 @@ test.describe("Radio Group", () => {
await expect(radios.nth(0)).toHaveAttribute("tabindex", "0");
});
test("should NOT set `tabindex` of 0 to a child radio if its value does not match the radiogroup `value`", async () => {
await root.evaluate(node => {
test("should NOT set `tabindex` of 0 to a child radio if its value does not match the radiogroup `value`", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group value="foo">
<fast-radio value="bar"></fast-radio>
@ -349,8 +411,16 @@ test.describe("Radio Group", () => {
).toBeTruthy();
});
test("should set a child radio with a matching `value` to `checked`", async () => {
await root.evaluate(node => {
test("should set a child radio with a matching `value` to `checked`", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group value="bar">
<fast-radio value="foo"></fast-radio>
@ -367,8 +437,16 @@ test.describe("Radio Group", () => {
await expect(radios.nth(2)).not.toBeChecked();
});
test("should set a child radio with a matching `value` to `checked` when value changes", async () => {
await root.evaluate(node => {
test("should set a child radio with a matching `value` to `checked` when value changes", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group value="foo">
<fast-radio value="foo"></fast-radio>
@ -389,8 +467,16 @@ test.describe("Radio Group", () => {
await expect(radios.nth(2)).not.toBeChecked();
});
test("should mark only the last radio defaulted to checked as checked", async () => {
await root.evaluate(node => {
test("should mark only the last radio defaulted to checked as checked", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group>
<fast-radio value="foo" checked></fast-radio>
@ -413,8 +499,16 @@ test.describe("Radio Group", () => {
await expect(radios.nth(2)).toBeChecked();
});
test("should mark radio matching value on radio-group over any checked attributes", async () => {
await root.evaluate(node => {
test("should mark radio matching value on radio-group over any checked attributes", async ({
page,
}) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group value="foo">
<fast-radio value="foo"></fast-radio>
@ -443,8 +537,14 @@ test.describe("Radio Group", () => {
await expect(radios.nth(2)).not.toBeChecked();
});
test("should allow resetting of elements by the parent form", async () => {
await root.evaluate(node => {
test("should allow resetting of elements by the parent form", async ({ page }) => {
const element = page.locator("fast-radio-group");
const radios = element.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-radio-group>

Просмотреть файл

@ -1,2 +1,3 @@
export { FASTRadio } from "./radio.js";
export type { RadioControl, RadioOptions } from "./radio.js";
export { radioTemplate } from "./radio.template.js";
export { FASTRadio, RadioControl, RadioOptions } from "./radio.js";

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTRadio } from "./radio.js";
test.describe("Radio", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should have a role of `radio`", async ({ page }) => {
const element = page.locator("fast-radio");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-radio");
root = page.locator("#root");
await page.goto(fixtureURL("radio--radio"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `radio`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -32,8 +16,12 @@ test.describe("Radio", () => {
await expect(element).toHaveAttribute("role", "radio");
});
test("should set ARIA attributes to match the state", async () => {
await root.evaluate(node => {
test("should set ARIA attributes to match the state", async ({ page }) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -73,8 +61,12 @@ test.describe("Radio", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set a tabindex of 0 on the element", async () => {
await root.evaluate(node => {
test("should set a tabindex of 0 on the element", async ({ page }) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -83,18 +75,28 @@ test.describe("Radio", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should NOT set a tabindex when disabled is `true`", async () => {
await root.evaluate(node => {
test("should NOT set a tabindex when disabled is `true`", async ({ page }) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio disabled></fast-radio>
`;
});
await expect(element).toHaveAttribute("tabindex", "");
await expect(element).not.toHaveAttribute("tabindex");
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -105,8 +107,14 @@ test.describe("Radio", () => {
await expect(element).toHaveJSProperty("initialValue", "on");
});
test("should initialize to the provided value attribute if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -117,8 +125,14 @@ test.describe("Radio", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -129,8 +143,14 @@ test.describe("Radio", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value property if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value property if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio value="foo"></fast-radio>
`;
@ -139,8 +159,14 @@ test.describe("Radio", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should set the `label__hidden` class on the internal label when default slotted content does not exist", async () => {
await root.evaluate(node => {
test("should set the `label__hidden` class on the internal label when default slotted content does not exist", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>label</fast-radio>
`;
@ -157,8 +183,12 @@ test.describe("Radio", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should fire an event when spacebar is pressed", async () => {
await root.evaluate(node => {
test("should fire an event when spacebar is pressed", async ({ page }) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -182,8 +212,12 @@ test.describe("Radio", () => {
expect(wasPressed).toBeTruthy();
});
test("should NOT fire events when clicked", async () => {
await root.evaluate(node => {
test("should NOT fire events when clicked", async ({ page }) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio>Radio</fast-radio>
`;
@ -206,8 +240,14 @@ test.describe("Radio", () => {
expect(wasClicked).toBeFalsy();
});
test("should handle validity when the `required` attribute is present", async () => {
await root.evaluate(node => {
test("should handle validity when the `required` attribute is present", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-radio-group>
<fast-radio required name="name" value="test">Radio</fast-radio>
@ -227,8 +267,14 @@ test.describe("Radio", () => {
});
test.describe("whose parent form has its reset() method invoked", () => {
test("should set its checked property to false if the checked attribute is unset", async () => {
await root.evaluate(node => {
test("should set its checked property to false if the checked attribute is unset", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-radio>Radio</fast-radio>
@ -249,8 +295,14 @@ test.describe("Radio", () => {
await expect(element).not.toBeChecked();
});
test("should set its checked property to true if the checked attribute is set", async () => {
await root.evaluate(node => {
test("should set its checked property to true if the checked attribute is set", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-radio checked></fast-radio>
@ -277,8 +329,14 @@ test.describe("Radio", () => {
await expect(element).toBeChecked();
});
test("should put the control into a clean state, where `checked` attribute modifications modify the `checked` property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where `checked` attribute modifications modify the `checked` property prior to user or programmatic interaction", async ({
page,
}) => {
const element = page.locator("fast-radio");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-radio>Radio</fast-radio>

Просмотреть файл

@ -1,31 +1,8 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTSearch } from "./search.js";
test.describe("Search", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
element = page.locator("fast-search");
root = page.locator("#root");
control = element.locator(".control");
await page.goto(fixtureURL("search--search"));
});
test.afterAll(async () => {
await page.close();
});
test.describe("should set the boolean attribute on the internal input", () => {
const attributes = {
autofocus: true,
@ -36,8 +13,12 @@ test.describe("Search", () => {
};
for (const attribute of Object.keys(attributes)) {
test(`should set ${attribute}`, async () => {
await root.evaluate(
test(attribute, async ({ page }) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { attribute }) => {
node.innerHTML = /* html */ `
<fast-search ${attribute}>Search</fast-search>
@ -81,8 +62,15 @@ test.describe("Search", () => {
for (const [attribute, value] of Object.entries(attributes)) {
const attrToken = spinalCase(attribute);
test(`should set ${attrToken} to ${value}`, async () => {
await root.evaluate(node => {
test(`should set ${attrToken} to ${value}`, async ({ page }) => {
const element = page.locator("fast-search");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search>Search</fast-search>
`;
@ -100,8 +88,14 @@ test.describe("Search", () => {
}
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search>Search</fast-search>
`;
@ -110,8 +104,14 @@ test.describe("Search", () => {
await expect(element).toHaveJSProperty("value", "");
});
test("should initialize to the provided value attribute if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search value="foo">Search</fast-search>
`;
@ -120,8 +120,14 @@ test.describe("Search", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search>Search</fast-search>
`;
@ -134,8 +140,14 @@ test.describe("Search", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value property if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value property if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const searchElement = document.createElement("fast-search") as FASTSearch;
@ -146,8 +158,14 @@ test.describe("Search", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should hide the label when no default slotted content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when no default slotted content is provided", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search></fast-search>
`;
@ -158,8 +176,12 @@ test.describe("Search", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when start content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when start content is provided", async ({ page }) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search><span slot="start">Start</span></fast-search>
`;
@ -170,8 +192,12 @@ test.describe("Search", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when end content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when end content is provided", async ({ page }) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search><span slot="end">End</span></fast-search>
`;
@ -182,8 +208,14 @@ test.describe("Search", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when start and end content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when start and end content is provided", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search>
<span slot="start">Start</span>
@ -197,8 +229,14 @@ test.describe("Search", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when space-only text nodes are slotted", async () => {
await root.evaluate(node => {
test("should hide the label when space-only text nodes are slotted", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search>label</fast-search>
`;
@ -215,15 +253,21 @@ test.describe("Search", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should fire a change event when the internal control emits a change event", async () => {
await root.evaluate(node => {
test("should fire a change event when the internal control emits a change event", async ({
page,
}) => {
const element = page.locator("fast-search");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-search>Search</fast-search>
`;
});
const control = element.locator(".control");
const [wasChanged] = await Promise.all([
element.evaluate(
node =>
@ -240,8 +284,14 @@ test.describe("Search", () => {
});
test.describe("when the owning form's reset() method is invoked", () => {
test("should reset its `value` property to an empty string when no value attribute is set", async () => {
await root.evaluate(node => {
test("should reset its `value` property to an empty string when no value attribute is set", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-search>Search</fast-search>
@ -268,8 +318,14 @@ test.describe("Search", () => {
await expect(element).toHaveJSProperty("value", "");
});
test("should reset its `value` property to the value of the value attribute if it is set", async () => {
await root.evaluate(node => {
test("should reset its `value` property to the value of the value attribute if it is set", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-search value="test value">Search</fast-search>
@ -292,8 +348,14 @@ test.describe("Search", () => {
await expect(element).toHaveJSProperty("value", "test value");
});
test("should put the control into a clean state, where `value` attribute modifications change the `value` property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where `value` attribute modifications change the `value` property prior to user or programmatic interaction", async ({
page,
}) => {
const element = page.locator("fast-search");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-search>Search</fast-search>

Просмотреть файл

@ -1,32 +1,14 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import type { FASTListboxOption } from "../listbox-option/index.js";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTSelect } from "./select.js";
test.describe("Select", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should have a role of `combobox`", async ({ page }) => {
const element = page.locator("fast-select");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-select");
root = page.locator("#root");
await page.goto(fixtureURL("select--select"), {
waitUntil: "load",
});
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `combobox`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select></fast-select>
`;
@ -35,8 +17,14 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("role", "combobox");
});
test("should have a tabindex of 0 when `disabled` is not defined", async () => {
await root.evaluate(node => {
test("should have a tabindex of 0 when `disabled` is not defined", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -49,8 +37,14 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should set the `aria-disabled` attribute equal to the `disabled` value", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to the `disabled` value", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select disabled></fast-select>
`;
@ -65,8 +59,12 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should have the attribute aria-expanded set to false", async () => {
await root.evaluate(node => {
test("should have the attribute aria-expanded set to false", async ({ page }) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select></fast-select>
`;
@ -75,8 +73,12 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("aria-expanded", "false");
});
test("should set its value to the first enabled option", async () => {
await root.evaluate(node => {
test("should set its value to the first enabled option", async ({ page }) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -91,8 +93,12 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("selectedIndex", 0);
});
test("should NOT have a tabindex when `disabled` is true", async () => {
await root.evaluate(node => {
test("should NOT have a tabindex when `disabled` is true", async ({ page }) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select disabled></fast-select>
`;
@ -101,8 +107,14 @@ test.describe("Select", () => {
await expect(element).not.hasAttribute("tabindex");
});
test("should set its value to the first enabled option when disabled", async () => {
await root.evaluate(node => {
test("should set its value to the first enabled option when disabled", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select disabled>
<fast-option>Option 1</fast-option>
@ -117,8 +129,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("selectedIndex", 0);
});
test("should select the first option with a `selected` attribute", async () => {
await root.evaluate(node => {
test("should select the first option with a `selected` attribute", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -133,8 +151,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("selectedIndex", 1);
});
test("should select the first option with a `selected` attribute when disabled", async () => {
await root.evaluate(node => {
test("should select the first option with a `selected` attribute when disabled", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select disabled>
<fast-option>Option 1</fast-option>
@ -149,8 +173,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("selectedIndex", 1);
});
test("should return the same value when the `value` property is set before connect", async () => {
await root.evaluate(node => {
test("should return the same value when the `value` property is set before connect", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select value="Option 2">
<fast-option>Option 1</fast-option>
@ -163,8 +193,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("value", "Option 2");
});
test("should return the same value when the value property is set after connect", async () => {
await root.evaluate(node => {
test("should return the same value when the value property is set after connect", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -181,8 +217,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("value", "Option 3");
});
test("should select the next selectable option when the value is set to match a disabled option", async () => {
await root.evaluate(node => {
test("should select the next selectable option when the value is set to match a disabled option", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -205,8 +247,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("selectedIndex", 2);
});
test("should update the `value` property when the selected option's `value` property changes", async () => {
await root.evaluate(node => {
test("should update the `value` property when the selected option's `value` property changes", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -227,8 +275,12 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("value", "new value");
});
test("should return the `value` property as a string", async () => {
await root.evaluate(node => {
test("should return the `value` property as a string", async ({ page }) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option value="1">Option 1</fast-option>
@ -245,8 +297,12 @@ test.describe("Select", () => {
).toBe("string");
});
test("should update the aria-expanded attribute when opened", async () => {
await root.evaluate(node => {
test("should update the aria-expanded attribute when opened", async ({ page }) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -265,8 +321,14 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("aria-expanded", "true");
});
test("should display the listbox when the `open` property is true before connecting", async () => {
await root.evaluate(node => {
test("should display the listbox when the `open` property is true before connecting", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select open>
<fast-option>Option 1</fast-option>
@ -290,8 +352,14 @@ test.describe("Select", () => {
{ expectedValue: "Option 3", key: "End" },
{ expectedValue: "Option 1", key: "Home" },
].forEach(({ expectedValue, key }) => {
test(`should NOT emit \`${eventName}\` event while open when the value changes by user input via ${key} key`, async () => {
await root.evaluate(node => {
test(`should NOT emit \`${eventName}\` event while open when the value changes by user input via ${key} key`, async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select open>
<fast-option>Option 1</fast-option>
@ -329,8 +397,14 @@ test.describe("Select", () => {
await expect(element).toHaveJSProperty("value", expectedValue);
});
test(`should emit \`${eventName}\` event while closed when the value changes by user input via ${key} key`, async () => {
await root.evaluate(node => {
test(`should emit \`${eventName}\` event while closed when the value changes by user input via ${key} key`, async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -361,8 +435,20 @@ test.describe("Select", () => {
test.describe("when the value changes by programmatic interaction", () => {
["input", "change"].forEach(eventName => {
test(`should NOT emit \`${eventName}\` event`, async () => {
await page.goto(fixtureURL("select--select"));
test(`should NOT emit \`${eventName}\` event`, async ({ page }) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
<fast-option>Option 2</fast-option>
<fast-option>Option 3</fast-option>
</fast-select>
`;
});
const [wasChanged] = await Promise.all([
element.evaluate(
@ -383,7 +469,7 @@ test.describe("Select", () => {
),
element.evaluate<void, FASTSelect>(node => {
node.value = "Tom Baker";
node.value = "Option 2";
}),
]);
@ -393,8 +479,14 @@ test.describe("Select", () => {
});
test.describe("when the owning form's reset() function is invoked", () => {
test("should reset the value property to the first available option", async () => {
await root.evaluate(node => {
test("should reset the value property to the first available option", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-select>
@ -424,8 +516,14 @@ test.describe("Select", () => {
});
});
test("should set the `aria-activedescendant` attribute to the ID of the currently selected option", async () => {
await root.evaluate(node => {
test("should set the `aria-activedescendant` attribute to the ID of the currently selected option", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option id="option-1">Option 1</fast-option>
@ -450,8 +548,14 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("aria-activedescendant", "option-3");
});
test("should set the `aria-controls` attribute to the ID of the internal listbox element while open", async () => {
await root.evaluate(node => {
test("should set the `aria-controls` attribute to the ID of the internal listbox element while open", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>
@ -480,8 +584,14 @@ test.describe("Select", () => {
await expect(element).toHaveAttribute("aria-controls", "");
});
test("should update the `displayValue` when the selected option's content changes", async () => {
await root.evaluate(node => {
test("should update the `displayValue` when the selected option's content changes", async ({
page,
}) => {
const element = page.locator("fast-select");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-select>
<fast-option>Option 1</fast-option>

Просмотреть файл

@ -1,5 +1,5 @@
import { css, ElementStyles } from "@microsoft/fast-element";
import chevronIcon from "../../../statics/svg/chevron_down_12_regular.svg";
import indicator from "../../../statics/svg/chevron_down_12_regular.svg";
import { FASTSelect } from "../select.js";
import { selectTemplate } from "../select.template.js";
@ -12,6 +12,7 @@ const styles = css`
border: calc(var(--stroke-width) * 1px) solid var(--accent-fill-rest);
box-sizing: border-box;
color: var(--neutral-foreground-rest);
fill: currentcolor;
font-family: var(--body-font);
height: calc(var(--height-number) * 1px);
position: relative;
@ -206,7 +207,7 @@ export class Select extends FASTSelect {
Select.define({
name: "fast-select",
template: selectTemplate({
indicator: chevronIcon,
indicator,
}),
styles,
});

Просмотреть файл

@ -1,30 +1,16 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTSliderLabel } from "./slider-label.js";
// TODO: Need to add tests for positioning and slider configuration
test.describe("Slider label", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should NOT set the `aria-disabled` attribute when `disabled` is not defined", async ({
page,
}) => {
const element = page.locator("fast-slider-label");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-slider-label");
root = page.locator("#root");
await page.goto(fixtureURL("slider-label--slider-label"));
});
test.afterAll(async () => {
await page.close();
});
test("should NOT set the `aria-disabled` attribute when `disabled` is not defined", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider-label></fast-slider-label>
`;
@ -33,8 +19,14 @@ test.describe("Slider label", () => {
await expect(element).not.hasAttribute("aria-disabled");
});
test("should set the `aria-disabled` attribute when the `disabled` property is true", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute when the `disabled` property is true", async ({
page,
}) => {
const element = page.locator("fast-slider-label");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider-label disabled></fast-slider-label>
`;

Просмотреть файл

@ -1,33 +1,16 @@
import { Direction } from "@microsoft/fast-web-utilities";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTSlider } from "./slider.js";
import { SliderOrientation } from "./slider.options.js";
// TODO: Need to add tests for keyboard handling, position, and focus management
test.describe("Slider", () => {
let page: Page;
let element: Locator;
test("should have a role of `slider`", async ({ page }) => {
const element = page.locator("fast-slider");
let root: Locator;
await page.goto("http://localhost:6006");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto(fixtureURL("slider--slider"));
element = page.locator("fast-slider");
root = page.locator("#root");
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `slider`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -35,8 +18,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("role", "slider");
});
test("should set a default `min` property of 0 when `min` is not provided", async () => {
await root.evaluate(node => {
test("should set a default `min` property of 0 when `min` is not provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -45,8 +34,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("min", 0);
});
test("should set a default `max` property of 0 when `max` is not provided", async () => {
await root.evaluate(node => {
test("should set a default `max` property of 0 when `max` is not provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -55,8 +50,12 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("max", "10");
});
test("should set a `tabindex` of 0", async () => {
await root.evaluate(node => {
test("should set a `tabindex` of 0", async ({ page }) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -65,8 +64,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should NOT set a default `aria-disabled` value when `disabled` is not defined", async () => {
await root.evaluate(node => {
test("should NOT set a default `aria-disabled` value when `disabled` is not defined", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -75,8 +80,14 @@ test.describe("Slider", () => {
await expect(element).not.hasAttribute("aria-disabled");
});
test("should set a default `aria-orientation` value when `orientation` is not defined", async () => {
await root.evaluate(node => {
test("should set a default `aria-orientation` value when `orientation` is not defined", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -88,8 +99,14 @@ test.describe("Slider", () => {
);
});
test("should NOT set a default `aria-readonly` value when `readonly` is not defined", async () => {
await root.evaluate(node => {
test("should NOT set a default `aria-readonly` value when `readonly` is not defined", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -98,8 +115,14 @@ test.describe("Slider", () => {
await expect(element).not.hasAttribute("aria-readonly");
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -112,8 +135,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", initialValue);
});
test("should set the `aria-disabled` attribute when `disabled` value is true", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute when `disabled` value is true", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -126,8 +155,12 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-disabled", "true");
});
test("should NOT set a tabindex when `disabled` value is true", async () => {
await root.evaluate(node => {
test("should NOT set a tabindex when `disabled` value is true", async ({ page }) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -140,8 +173,14 @@ test.describe("Slider", () => {
await expect(element).not.toHaveAttribute("tabindex", "0");
});
test("should set the `aria-readonly` attribute when `readonly` value is true", async () => {
await root.evaluate(node => {
test("should set the `aria-readonly` attribute when `readonly` value is true", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -154,8 +193,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-readonly", "true");
});
test("should set the `aria-orientation` attribute equal to the `orientation` value", async () => {
await root.evaluate(node => {
test("should set the `aria-orientation` attribute equal to the `orientation` value", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -180,8 +225,12 @@ test.describe("Slider", () => {
);
});
test("should set direction equal to the `direction` value", async () => {
await root.evaluate(node => {
test("should set direction equal to the `direction` value", async ({ page }) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -200,8 +249,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("direction", Direction.rtl);
});
test("should set the `aria-valuenow` attribute with the `value` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuenow` attribute with the `value` property when provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -214,8 +269,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-valuenow", "8");
});
test("should set the `aria-valuemin` attribute with the `min` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuemin` attribute with the `min` property when provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -228,8 +289,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-valuemin", "0");
});
test("should set the `aria-valuemax` attribute with the `max` property when provided", async () => {
await root.evaluate(node => {
test("should set the `aria-valuemax` attribute with the `max` property when provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -243,8 +310,12 @@ test.describe("Slider", () => {
});
test.describe("valueAsNumber", () => {
test("should allow setting value with number", async () => {
await root.evaluate(node => {
test("should allow setting value with number", async ({ page }) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -257,8 +328,12 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "8");
});
test("should allow reading value as number", async () => {
await root.evaluate(node => {
test("should allow reading value as number", async ({ page }) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -272,8 +347,14 @@ test.describe("Slider", () => {
});
});
test("should set an `aria-valuestring` attribute with the result of the valueTextFormatter() method", async () => {
await root.evaluate(node => {
test("should set an `aria-valuestring` attribute with the result of the valueTextFormatter() method", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -287,8 +368,14 @@ test.describe("Slider", () => {
});
test.describe("increment and decrement methods", () => {
test("should increment the value when the `increment()` method is invoked", async () => {
await root.evaluate(node => {
test("should increment the value when the `increment()` method is invoked", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider min="0" max="100" value="50" step="5"></fast-slider>
`;
@ -305,8 +392,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-valuenow", "55");
});
test("should decrement the value when the `decrement()` method is invoked", async () => {
await root.evaluate(node => {
test("should decrement the value when the `decrement()` method is invoked", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider min="0" max="100" value="50" step="5"></fast-slider>
`;
@ -321,8 +414,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-valuenow", "45");
});
test("should increment the value when the `increment()` method is invoked and step is not provided", async () => {
await root.evaluate(node => {
test("should increment the value when the `increment()` method is invoked and step is not provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider min="0" max="100" value="50"></fast-slider>
`;
@ -339,8 +438,14 @@ test.describe("Slider", () => {
await expect(element).toHaveAttribute("aria-valuenow", "51");
});
test("should decrement the value when the `decrement()` method is invoked and step is not provided", async () => {
await root.evaluate(node => {
test("should decrement the value when the `decrement()` method is invoked and step is not provided", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider min="0" max="100" value="50"></fast-slider>
`;
@ -356,8 +461,14 @@ test.describe("Slider", () => {
});
});
test("should increase or decrease the slider value on arrow left/right keys", async () => {
await root.evaluate(node => {
test("should increase or decrease the slider value on arrow left/right keys", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-slider min="0" max="100"></fast-slider>
@ -386,8 +497,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "7");
});
test("should increase or decrease the slider value on arrow up/down keys", async () => {
await root.evaluate(node => {
test("should increase or decrease the slider value on arrow up/down keys", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-slider min="0" max="100"></fast-slider>
@ -416,8 +533,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "7");
});
test("should constrain and normalize the value between `min` and `max` when the value is out of range", async () => {
await root.evaluate(node => {
test("should constrain and normalize the value between `min` and `max` when the value is out of range", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider min="0" max="100"></fast-slider>
`;
@ -438,8 +561,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "0");
});
test("should initialize to the provided value attribute if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider value="4"></fast-slider>
`;
@ -450,8 +579,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "4");
});
test("should initialize to the provided value attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider></fast-slider>
`;
@ -464,8 +599,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "3");
});
test("should initialize to the provided value property if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value property if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const slider = document.createElement("fast-slider") as FASTSlider;
@ -476,8 +617,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "3");
});
test("should update the `stepMultiplier` when the `step` attribute has been updated", async () => {
await root.evaluate(node => {
test("should update the `stepMultiplier` when the `step` attribute has been updated", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-slider step="2" value="4"></fast-slider>
`;
@ -498,8 +645,14 @@ test.describe("Slider", () => {
});
test.describe("when the owning form's reset() method is invoked", () => {
test("should reset its `value` property to the midpoint if no `value` attribute is set", async () => {
await root.evaluate(node => {
test("should reset its `value` property to the midpoint if no `value` attribute is set", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-slider></fast-slider>
@ -513,7 +666,7 @@ test.describe("Slider", () => {
node.value = "3";
});
await expect(element).toHaveAttribute("value", "");
await expect(element).not.toHaveAttribute("value");
await expect(element).toHaveJSProperty("value", "3");
@ -524,8 +677,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "5");
});
test("should reset its `value` property to match the `value` attribute when it is set", async () => {
await root.evaluate(node => {
test("should reset its `value` property to match the `value` attribute when it is set", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-slider min="0" max="100"></fast-slider>
@ -544,6 +703,7 @@ test.describe("Slider", () => {
});
await expect(element).toHaveAttribute("value", "7");
await expect(element).toHaveJSProperty("value", "8");
await form.evaluate<void, HTMLFormElement>(node => {
@ -553,8 +713,14 @@ test.describe("Slider", () => {
await expect(element).toHaveJSProperty("value", "7");
});
test("should put the control into a clean state, where the value attribute changes the value property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where the value attribute changes the value property prior to user or programmatic interaction", async ({
page,
}) => {
const element = page.locator("fast-slider");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-slider min="0" max="100"></fast-slider>

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTSwitch } from "./switch.js";
test.describe("Switch", () => {
let page: Page;
let root: Locator;
let element: Locator;
test("should have a role of `switch`", async ({ page }) => {
const element = page.locator("fast-switch");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-switch");
root = page.locator("#root");
await page.goto(fixtureURL("switch--switch"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `switch`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -32,8 +16,12 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("role", "switch");
});
test("should set a tabindex of 0 on the element", async () => {
await root.evaluate(node => {
test("should set a tabindex of 0 on the element", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -42,8 +30,14 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should set a default `aria-checked` value when `checked` is not defined", async () => {
await root.evaluate(node => {
test("should set a default `aria-checked` value when `checked` is not defined", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -52,8 +46,14 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should set a default `aria-disabled` value when `disabled` is not defined", async () => {
await root.evaluate(node => {
test("should set a default `aria-disabled` value when `disabled` is not defined", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -62,8 +62,14 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should NOT set a default `aria-readonly` value when `readonly` is not defined", async () => {
await root.evaluate(node => {
test("should NOT set a default `aria-readonly` value when `readonly` is not defined", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -72,8 +78,14 @@ test.describe("Switch", () => {
await expect(element).not.hasAttribute("aria-readonly");
});
test("should set the `aria-checked` attribute equal to the `checked` property", async () => {
await root.evaluate(node => {
test("should set the `aria-checked` attribute equal to the `checked` property", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -92,8 +104,14 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("aria-checked", "false");
});
test("should set the `aria-readonly` attribute equal to the `readonly` value", async () => {
await root.evaluate(node => {
test("should set the `aria-readonly` attribute equal to the `readonly` value", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -112,8 +130,14 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("aria-readonly", "false");
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -126,8 +150,14 @@ test.describe("Switch", () => {
await expect(element).toHaveJSProperty("value", initialValue);
});
test("should add a class of `label` to the internal label when default slotted content exists", async () => {
await root.evaluate(node => {
test("should add a class of `label` to the internal label when default slotted content exists", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -143,8 +173,14 @@ test.describe("Switch", () => {
await expect(label).not.toHaveClass(/label__hidden/);
});
test("should add classes of `label` and `label__hidden` to the internal label when default slotted content exists", async () => {
await root.evaluate(node => {
test("should add classes of `label` and `label__hidden` to the internal label when default slotted content exists", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch>Switch</fast-switch>
`;
@ -161,8 +197,14 @@ test.describe("Switch", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should set the `aria-disabled` attribute equal to the `disabled` value", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to the `disabled` value", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -181,8 +223,12 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should NOT set a tabindex when disabled is `true`", async () => {
await root.evaluate(node => {
test("should NOT set a tabindex when disabled is `true`", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch disabled></fast-switch>
`;
@ -197,8 +243,14 @@ test.describe("Switch", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should initialize to the provided value attribute if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch value="foo"></fast-switch>
`;
@ -207,8 +259,14 @@ test.describe("Switch", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -221,8 +279,14 @@ test.describe("Switch", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value property if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value property if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const switchElement = document.createElement("fast-switch") as FASTSwitch;
@ -233,8 +297,12 @@ test.describe("Switch", () => {
await expect(element).toHaveJSProperty("value", "foobar");
});
test("should emit an event when clicked", async () => {
await root.evaluate(node => {
test("should emit an event when clicked", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -255,8 +323,12 @@ test.describe("Switch", () => {
expect(wasClicked).toBe(true);
});
test("should fire an event when spacebar is invoked", async () => {
await root.evaluate(node => {
test("should fire an event when spacebar is invoked", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -277,8 +349,12 @@ test.describe("Switch", () => {
expect(wasEmitted).toBe(true);
});
test("should fire an event when enter is invoked", async () => {
await root.evaluate(node => {
test("should fire an event when enter is invoked", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-switch></fast-switch>
`;
@ -299,8 +375,12 @@ test.describe("Switch", () => {
expect(wasEmitted).toBe(true);
});
test("should be invalid when required and unchecked", async () => {
await root.evaluate(node => {
test("should be invalid when required and unchecked", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-switch required></fast-switch>
@ -315,8 +395,12 @@ test.describe("Switch", () => {
).toBe(true);
});
test("should be valid when required and checked", async () => {
await root.evaluate(node => {
test("should be valid when required and checked", async ({ page }) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-switch required checked></fast-switch>
@ -332,8 +416,14 @@ test.describe("Switch", () => {
});
test.describe("who's parent form has it's reset() method invoked", () => {
test("should set its checked property to false if the checked attribute is unset", async () => {
await root.evaluate(node => {
test("should set its checked property to false if the checked attribute is unset", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-switch></fast-switch>
@ -358,8 +448,14 @@ test.describe("Switch", () => {
await expect(element).toHaveJSProperty("checked", false);
});
test("should set its checked property to true if the checked attribute is set", async () => {
await root.evaluate(node => {
test("should set its checked property to true if the checked attribute is set", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-switch checked></fast-switch>
@ -384,8 +480,14 @@ test.describe("Switch", () => {
await expect(element).toHaveJSProperty("checked", true);
});
test("should put the control into a clean state, where `checked` attribute modifications update the `checked` property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where `checked` attribute modifications update the `checked` property prior to user or programmatic interaction", async ({
page,
}) => {
const element = page.locator("fast-switch");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-switch></fast-switch>

Просмотреть файл

@ -1,28 +1,12 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
test.describe("TabPanel", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should have a role of `tabpanel`", async ({ page }) => {
const element = page.locator("fast-tab-panel");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-tab-panel");
root = page.locator("#root");
await page.goto(fixtureURL("tabs-tab-panel--tab-panel"));
});
test.afterAll(async () => {
await page.close();
});
test("should have a role of `tabpanel`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tab-panel></fast-tab-panel>
`;
@ -31,8 +15,12 @@ test.describe("TabPanel", () => {
await expect(element).toHaveAttribute("role", "tabpanel");
});
test("should have a slot attribute of `tabpanel`", async () => {
await root.evaluate(node => {
test("should have a slot attribute of `tabpanel`", async ({ page }) => {
const element = page.locator("fast-tab-panel");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tab-panel></fast-tab-panel>
`;

Просмотреть файл

@ -1,47 +1,60 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTTab } from "./tab.js";
test.describe("Tab", () => {
test.describe("States, attributes, and properties", () => {
let page: Page;
let element: Locator;
test("should have a role of `tab`", async ({ page }) => {
const element = page.locator("fast-tab");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
await page.goto(fixtureURL("tabs-tab--tab"));
element = page.locator("fast-tab");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tab></fast-tab>
`;
});
test.afterAll(async () => {
await page.close();
await expect(element).toHaveAttribute("role", "tab");
});
test("should have a slot attribute of `tab`", async ({ page }) => {
const element = page.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tab></fast-tab>
`;
});
test("should have a role of `tab`", async () => {
await expect(element).toHaveAttribute("role", "tab");
await expect(element).toHaveAttribute("slot", "tab");
});
test("should set the `aria-disabled` attribute when `disabled` is true", async ({
page,
}) => {
const element = page.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tab></fast-tab>
`;
});
test("should have a slot attribute of `tab`", async () => {
await expect(element).toHaveAttribute("slot", "tab");
await expect(element).not.hasAttribute("aria-disabled");
await element.evaluate<void, FASTTab>(node => {
node.disabled = true;
});
test("should set the `aria-disabled` attribute when `disabled` is true", async () => {
await expect(element).not.hasAttribute("aria-disabled");
await expect(element).toHaveAttribute("aria-disabled", "true");
await element.evaluate<void, FASTTab>(node => {
node.disabled = true;
});
await expect(element).toHaveAttribute("aria-disabled", "true");
await element.evaluate<void, FASTTab>(element => {
element.disabled = false;
});
await expect(element).toHaveAttribute("aria-disabled", "false");
await element.evaluate<void, FASTTab>(element => {
element.disabled = false;
});
await expect(element).toHaveAttribute("aria-disabled", "false");
});
});

Просмотреть файл

@ -1,17 +1,9 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import type { FASTTab } from "../tab/tab.js";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTTabs } from "./tabs.js";
// TODO: Need to add tests for keyboard handling, activeIndicator position, and focus management
test.describe("Tabs", () => {
let page: Page;
let element: Locator;
let root: Locator;
let tablist: Locator;
let tabs: Locator;
const template = /* html */ `
<fast-tabs>
<fast-tab>Tab one</fast-tab>
@ -23,26 +15,14 @@ test.describe("Tabs", () => {
</fast-tabs>
`;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
test("should have an internal element with a role of `tablist`", async ({ page }) => {
const element = page.locator("fast-tabs");
element = page.locator("fast-tabs");
const tablist = element.locator(".tablist");
root = page.locator("#root");
await page.goto("http://localhost:6006");
tablist = element.locator(".tablist");
tabs = element.locator("fast-tab");
await page.goto(fixtureURL("tabs--tabs"));
});
test.afterAll(async () => {
await page.close();
});
test("should have an internal element with a role of `tablist`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs></fast-tabs>
`;
@ -51,8 +31,14 @@ test.describe("Tabs", () => {
await expect(tablist).toHaveAttribute("role", "tablist");
});
test("should set a default orientation value of `horizontal` when `orientation` is not provided", async () => {
await root.evaluate(node => {
test("should set a default orientation value of `horizontal` when `orientation` is not provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs></fast-tabs>
`;
@ -61,8 +47,16 @@ test.describe("Tabs", () => {
await expect(element).toHaveJSProperty("orientation", "horizontal");
});
test("should set an `id` attribute on the active tab when an `id` is provided", async () => {
await root.evaluate(node => {
test("should set an `id` attribute on the active tab when an `id` is provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs></fast-tabs>
`;
@ -81,8 +75,16 @@ test.describe("Tabs", () => {
}
});
test("should set an `id` attribute on tab items with a unique ID when an `id` is NOT provided", async () => {
await root.evaluate(
test("should set an `id` attribute on tab items with a unique ID when an `id` is NOT provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
@ -105,8 +107,16 @@ test.describe("Tabs", () => {
}
});
test("should set `aria-labelledby` on the tab panel and `aria-controls` on the tab which corresponds to the matching ID when IDs are NOT provided", async () => {
await root.evaluate(
test("should set `aria-labelledby` on the tab panel and `aria-controls` on the tab which corresponds to the matching ID when IDs are NOT provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
@ -136,8 +146,16 @@ test.describe("Tabs", () => {
}
});
test("should set `aria-labelledby` on the tab panel and `aria-controls` on the tab which corresponds to the matching ID when IDs are NOT provided and additional tabs and panels are added", async () => {
await root.evaluate(
test("should set `aria-labelledby` on the tab panel and `aria-controls` on the tab which corresponds to the matching ID when IDs are NOT provided and additional tabs and panels are added", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
@ -201,106 +219,145 @@ test.describe("Tabs", () => {
}
});
test.describe("active tab", () => {
test("should set an `aria-selected` attribute on the active tab when `activeid` is provided", async () => {
await root.evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
test("should set an `aria-selected` attribute on the active tab when `activeid` is provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const secondTab = tabs.nth(1);
const tabs = element.locator("fast-tab");
const secondTabId = (await secondTab.getAttribute("id")) ?? "";
await page.goto("http://localhost:6006");
await element.evaluate((node: FASTTabs, secondTabId) => {
node.activeid = secondTabId;
}, secondTabId);
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
await expect(secondTab).toHaveAttribute("aria-selected", "true");
});
const secondTab = tabs.nth(1);
test("should default the first tab as the active index if `activeid` is NOT provided", async () => {
await root.evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
const secondTabId = (await secondTab.getAttribute("id")) ?? "";
await expect(tabs.nth(0)).toHaveAttribute("aria-selected", "true");
await element.evaluate((node: FASTTabs, secondTabId) => {
node.activeid = secondTabId;
}, secondTabId);
await expect(element).toHaveJSProperty("activeTabIndex", 0);
});
test("should update `aria-selected` attribute on the active tab when `activeId` is updated", async () => {
await root.evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
await expect(tabs.nth(0)).toHaveAttribute("aria-selected", "true");
const secondTab = tabs.nth(1);
const secondTabId = (await secondTab.getAttribute("id")) ?? "";
await element.evaluate((node: FASTTabs, secondTabId) => {
node.setAttribute("activeId", secondTabId);
}, secondTabId);
await expect(secondTab).toHaveAttribute("aria-selected", "true");
});
await expect(secondTab).toHaveAttribute("aria-selected", "true");
});
test.describe("active tabpanel", () => {
test("should set an `aria-labelledby` attribute on the tabpanel with a value of the tab id when `activeid` is provided", async () => {
await root.evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
test("should default the first tab as the active index if `activeid` is NOT provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const secondTab = tabs.nth(1);
const tabs = element.locator("fast-tab");
const secondTabId = (await secondTab.getAttribute("id")) ?? "";
await page.goto("http://localhost:6006");
const tabPanels = element.locator("fast-tab-panel");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
await element.evaluate((node: FASTTabs, secondTabId) => {
node.activeid = secondTabId;
}, secondTabId);
await expect(tabs.nth(0)).toHaveAttribute("aria-selected", "true");
await expect(tabPanels.nth(1)).toHaveAttribute(
"aria-labelledby",
secondTabId
);
});
test("should set an attribute of hidden if the tabpanel is not active", async () => {
await root.evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
const tabPanels = element.locator("fast-tab-panel");
await expect(tabPanels.nth(0)).not.toHaveBooleanAttribute("hidden");
await expect(tabPanels.nth(1)).toHaveBooleanAttribute("hidden");
await expect(tabPanels.nth(2)).toHaveBooleanAttribute("hidden");
});
await expect(element).toHaveJSProperty("activeTabIndex", 0);
});
test("should not allow selecting a tab that has been disabled after it has been connected", async () => {
await root.evaluate(node => {
test("should update `aria-selected` attribute on the active tab when `activeId` is updated", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
await expect(tabs.nth(0)).toHaveAttribute("aria-selected", "true");
const secondTab = tabs.nth(1);
const secondTabId = (await secondTab.getAttribute("id")) ?? "";
await element.evaluate((node: FASTTabs, secondTabId) => {
node.setAttribute("activeId", secondTabId);
}, secondTabId);
await expect(secondTab).toHaveAttribute("aria-selected", "true");
});
test("should set an `aria-labelledby` attribute on the tabpanel with a value of the tab id when `activeid` is provided", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
const secondTab = tabs.nth(1);
const secondTabId = (await secondTab.getAttribute("id")) ?? "";
const tabPanels = element.locator("fast-tab-panel");
await element.evaluate((node: FASTTabs, secondTabId) => {
node.activeid = secondTabId;
}, secondTabId);
await expect(tabPanels.nth(1)).toHaveAttribute("aria-labelledby", secondTabId);
});
test("should set the `hidden` attribute if the tabpanel is not active", async ({
page,
}) => {
const element = page.locator("fast-tabs");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { template }) => {
node.innerHTML = template;
},
{ template }
);
const tabPanels = element.locator("fast-tab-panel");
await expect(tabPanels.nth(0)).not.toHaveBooleanAttribute("hidden");
await expect(tabPanels.nth(1)).toHaveBooleanAttribute("hidden");
await expect(tabPanels.nth(2)).toHaveBooleanAttribute("hidden");
});
test("should not allow selecting a tab that has been disabled after it has been connected", async ({
page,
}) => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs>
<fast-tab id="tab-1">Tab one</fast-tab>
@ -342,10 +399,18 @@ test.describe("Tabs", () => {
await expect(element).toHaveJSProperty("activeid", firstTabId);
});
test("should allow selecting tab that has been enabled after it has been connected", async () => {
test("should allow selecting tab that has been enabled after it has been connected", async ({
page,
}) => {
test.slow();
await root.evaluate(node => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs>
<fast-tab>Tab one</fast-tab>
@ -387,10 +452,16 @@ test.describe("Tabs", () => {
await expect(element).toHaveJSProperty("activeid", secondTabId);
});
test("should not allow selecting hidden tab using arrow keys", async () => {
test("should not allow selecting hidden tab using arrow keys", async ({ page }) => {
test.slow();
await root.evaluate(node => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs>
<fast-tab>Tab one</fast-tab>
@ -420,10 +491,16 @@ test.describe("Tabs", () => {
await expect(element).toHaveJSProperty("activeid", thirdTabId);
});
test("should not allow selecting hidden tab by pressing End", async () => {
test("should not allow selecting hidden tab by pressing End", async ({ page }) => {
test.slow();
await root.evaluate(node => {
const element = page.locator("fast-tabs");
const tabs = element.locator("fast-tab");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tabs>
<fast-tab>Tab one</fast-tab>

Просмотреть файл

@ -1,31 +1,8 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTTextArea } from "./text-area.js";
test.describe("TextArea", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
element = page.locator("fast-text-area");
root = page.locator("#root");
control = element.locator(".control");
await page.goto(fixtureURL("text-area--text-area"));
});
test.afterAll(async () => {
await page.close();
});
test.describe("should set the boolean attribute on the internal input", () => {
const attributes = {
autofocus: true,
@ -36,8 +13,14 @@ test.describe("TextArea", () => {
};
for (const attribute of Object.keys(attributes)) {
test(attribute, async () => {
await root.evaluate((node, attribute: string) => {
test(attribute, async ({ page }) => {
const element = page.locator("fast-text-area");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate((node, attribute: string) => {
node.innerHTML = /* html */ `
<fast-text-area ${attribute}></fast-text-area>
`;
@ -81,8 +64,14 @@ test.describe("TextArea", () => {
for (const [attribute, value] of Object.entries(attributes)) {
const attributeSpinalCase = spinalCase(attribute);
test(attribute, async () => {
await root.evaluate(
test(attribute, async ({ page }) => {
const element = page.locator("fast-text-area");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { attributeSpinalCase, value }) => {
node.innerHTML = /* html */ `
<fast-text-area ${attributeSpinalCase}="${value}"></fast-text-area>
@ -96,8 +85,14 @@ test.describe("TextArea", () => {
}
});
test("should initialize to the initial value if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the initial value if no value property is set", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-area></fast-text-area>
`;
@ -106,8 +101,14 @@ test.describe("TextArea", () => {
await expect(element).toHaveJSProperty("value", "");
});
test("should initialize to the provided value attribute if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-area value="foo"></fast-text-area>
`;
@ -116,8 +117,14 @@ test.describe("TextArea", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value property if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value property if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "";
const textArea = document.createElement("fast-text-area") as FASTTextArea;
@ -128,8 +135,14 @@ test.describe("TextArea", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided value attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided value attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-area></fast-text-area>
`;
@ -142,8 +155,16 @@ test.describe("TextArea", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should fire a `change` event when the internal control emits a `change` event", async () => {
await root.evaluate(node => {
test("should fire a `change` event when the internal control emits a `change` event", async ({
page,
}) => {
const element = page.locator("fast-text-area");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-area></fast-text-area>
`;
@ -169,8 +190,14 @@ test.describe("TextArea", () => {
});
test.describe("when the owning form's reset() method is invoked", () => {
test("should reset the `value` property to an empty string when no `value` attribute is set", async () => {
await root.evaluate(node => {
test("should reset the `value` property to an empty string when no `value` attribute is set", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-text-area></fast-text-area>
@ -197,8 +224,14 @@ test.describe("TextArea", () => {
await expect(element).toHaveJSProperty("value", "");
});
test("should reset the `value` property to match the `value` attribute", async () => {
await root.evaluate(node => {
test("should reset the `value` property to match the `value` attribute", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-text-area value="foo"></fast-text-area>
@ -225,8 +258,14 @@ test.describe("TextArea", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should put the control into a clean state, where `value` attribute modifications change the `value` property prior to user or programmatic interaction", async () => {
await root.evaluate(node => {
test("should put the control into a clean state, where `value` attribute modifications change the `value` property prior to user or programmatic interaction", async ({
page,
}) => {
const element = page.locator("fast-text-area");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<form>
<fast-text-area></fast-text-area>

Просмотреть файл

@ -104,35 +104,37 @@ This component is built with the expectation that focus is delegated to the inpu
#### Fields
| Name | Privacy | Type | Default | Description | Inherited From |
| ------------- | ------- | --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `readOnly` | public | `boolean` | | When true, the control will be immutable by user interaction. See [readonly HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly) for more information. | |
| `autofocus` | public | `boolean` | | Indicates that this element should get focus after the page finishes loading. See [autofocus HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautofocus) for more information. | |
| `placeholder` | public | `string` | | Sets the placeholder value of the element, generally used to provide a hint to the user. | |
| `type` | public | `TextFieldType` | | Allows setting a type or mode of text. | |
| `list` | public | `string` | | Allows associating a [datalist](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist) to the element by https://developer.mozilla.org/en-US/docs/Web/API/Element/id. | |
| `maxlength` | public | `number` | | The maximum number of characters a user can enter. | |
| `minlength` | public | `number` | | The minimum number of characters a user can enter. | |
| `pattern` | public | `string` | | A regular expression that the value must match to pass validation. | |
| `size` | public | `number` | | Sets the width of the element to a specified number of characters. | |
| `spellcheck` | public | `boolean` | | Controls whether or not to enable spell checking for the input field, or if the default spell checking configuration should be used. | |
| `proxy` | | | | | FormAssociatedTextField |
| Name | Privacy | Type | Default | Description | Inherited From |
| ----------------- | ------- | ------------------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| `readOnly` | public | `boolean` | | When true, the control will be immutable by user interaction. See [readonly HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly) for more information. | |
| `autofocus` | public | `boolean` | | Indicates that this element should get focus after the page finishes loading. See [autofocus HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#htmlattrdefautofocus) for more information. | |
| `placeholder` | public | `string` | | Sets the placeholder value of the element, generally used to provide a hint to the user. | |
| `type` | public | `TextFieldType` | | Allows setting a type or mode of text. | |
| `slottedDataList` | public | `HTMLDataListElement[] or undefined` | | The slotted datalist elements. | |
| `list` | public | `string` | | Allows associating a [datalist](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist) to the element by https://developer.mozilla.org/en-US/docs/Web/API/Element/id. | |
| `maxlength` | public | `number` | | The maximum number of characters a user can enter. | |
| `minlength` | public | `number` | | The minimum number of characters a user can enter. | |
| `pattern` | public | `string` | | A regular expression that the value must match to pass validation. | |
| `size` | public | `number` | | Sets the width of the element to a specified number of characters. | |
| `spellcheck` | public | `boolean` | | Controls whether or not to enable spell checking for the input field, or if the default spell checking configuration should be used. | |
| `proxy` | | | | | FormAssociatedTextField |
#### Methods
| Name | Privacy | Description | Parameters | Return | Inherited From |
| -------------------- | --------- | ------------------------------------------------- | ---------- | ------ | -------------- |
| `readOnlyChanged` | protected | | | `void` | |
| `autofocusChanged` | protected | | | `void` | |
| `placeholderChanged` | protected | | | `void` | |
| `listChanged` | protected | | | `void` | |
| `maxlengthChanged` | protected | | | `void` | |
| `minlengthChanged` | protected | | | `void` | |
| `patternChanged` | protected | | | `void` | |
| `sizeChanged` | protected | | | `void` | |
| `spellcheckChanged` | protected | | | `void` | |
| `select` | public | Selects all the text in the text field | | `void` | |
| `validate` | public | {@inheritDoc (FormAssociated:interface).validate} | | `void` | |
| Name | Privacy | Description | Parameters | Return | Inherited From |
| ------------------------ | --------- | ------------------------------------------------- | ------------------------------------------------------------------------------------ | ------ | -------------- |
| `readOnlyChanged` | protected | | | `void` | |
| `autofocusChanged` | protected | | | `void` | |
| `placeholderChanged` | protected | | | `void` | |
| `slottedDataListChanged` | protected | | `prev: HTMLDataListElement[] or undefined, next: HTMLDataListElement[] or undefined` | `void` | |
| `listChanged` | protected | | | `void` | |
| `maxlengthChanged` | protected | | | `void` | |
| `minlengthChanged` | protected | | | `void` | |
| `patternChanged` | protected | | | `void` | |
| `sizeChanged` | protected | | | `void` | |
| `spellcheckChanged` | protected | | | `void` | |
| `select` | public | Selects all the text in the text field | | `void` | |
| `validate` | public | {@inheritDoc (FormAssociated:interface).validate} | | `void` | |
#### Events

Просмотреть файл

@ -112,3 +112,15 @@ export const TextFieldInForm: Story<FASTTextField> = renderComponent(
</form>
`
).bind({});
export const TextFieldWithDataList: Story<FASTTextField> = renderComponent(
html<StoryArgs<FASTTextField>>`
<fast-text-field>
<datalist id="test" slot="datalist">
<option value="test" />
<option value="test2" />
<option value="test3" />
</datalist>
</fast-text-field>
`
).bind({});

Просмотреть файл

@ -1,36 +1,18 @@
import { spinalCase } from "@microsoft/fast-web-utilities";
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTTextField } from "./text-field.js";
test.describe("TextField", () => {
let page: Page;
let element: Locator;
let root: Locator;
let control: Locator;
let label: Locator;
test("should set the `autofocus` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-text-field");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
const control = element.locator(".control");
element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
root = page.locator("#root");
control = element.locator(".control");
label = element.locator(".label");
await page.goto(fixtureURL("debug--blank"));
});
test.afterAll(async () => {
await page.close();
});
test("should set the `autofocus` attribute on the internal control", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field autofocus></fast-text-field>
`;
@ -39,8 +21,16 @@ test.describe("TextField", () => {
await expect(control).toHaveBooleanAttribute("autofocus");
});
test("should set the `disabled` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `disabled` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field disabled></fast-text-field>
`;
@ -49,8 +39,16 @@ test.describe("TextField", () => {
await expect(control).toHaveBooleanAttribute("disabled");
});
test("should set the `readonly` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `readonly` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field readonly></fast-text-field>
`;
@ -59,8 +57,16 @@ test.describe("TextField", () => {
await expect(control).toHaveBooleanAttribute("readonly");
});
test("should set the `required` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `required` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field required></fast-text-field>
`;
@ -69,8 +75,16 @@ test.describe("TextField", () => {
await expect(control).toHaveBooleanAttribute("required");
});
test("should set the `spellcheck` attribute on the internal control", async () => {
await root.evaluate(node => {
test("should set the `spellcheck` attribute on the internal control", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field spellcheck></fast-text-field>
`;
@ -111,8 +125,16 @@ test.describe("TextField", () => {
for (const [attribute, value] of Object.entries(attributes)) {
const attrToken = spinalCase(attribute);
test(`should set the \`${attrToken}\` attribute on the internal control`, async () => {
await root.evaluate(
test(`should set the \`${attrToken}\` attribute on the internal control`, async ({
page,
}) => {
const element = page.locator("fast-text-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(
(node, { attrToken, value }) => {
node.innerHTML = /* html */ `
<fast-text-field ${attrToken}="${value}"></fast-text-field>
@ -126,8 +148,14 @@ test.describe("TextField", () => {
}
});
test("should initialize to the `initialValue` property if no value property is set", async () => {
await root.evaluate(node => {
test("should initialize to the `initialValue` property if no value property is set", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field></fast-text-field>
`;
@ -140,20 +168,30 @@ test.describe("TextField", () => {
await expect(element).toHaveJSProperty("value", initialValue);
});
test("should initialize to the provided `value` attribute if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` attribute if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field value="foo"></fast-text-field>
`;
});
const element = page.locator("fast-text-field");
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided `value` property if set pre-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` property if set pre-connection", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ ``;
const textField = document.createElement("fast-text-field") as FASTTextField;
@ -166,8 +204,14 @@ test.describe("TextField", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should initialize to the provided `value` attribute if set post-connection", async () => {
await root.evaluate(node => {
test("should initialize to the provided `value` attribute if set post-connection", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field></fast-text-field>
`;
@ -180,16 +224,30 @@ test.describe("TextField", () => {
await expect(element).toHaveJSProperty("value", "foo");
});
test("should hide the label when no default slotted content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when no default slotted content is provided", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const label = element.locator(".label");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = "<fast-text-field></fast-text-field>";
});
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when start content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when start content is provided", async ({ page }) => {
const element = page.locator("fast-text-field");
const label = element.locator(".label");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field>
<div slot="start"></div>
@ -200,8 +258,14 @@ test.describe("TextField", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when end content is provided", async () => {
await root.evaluate(node => {
test("should hide the label when end content is provided", async ({ page }) => {
const element = page.locator("fast-text-field");
const label = element.locator(".label");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field>
<div slot="end"></div>
@ -212,8 +276,16 @@ test.describe("TextField", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when start and end content are provided", async () => {
await root.evaluate(node => {
test("should hide the label when start and end content are provided", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const label = element.locator(".label");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field>
<div slot="start"></div>
@ -225,8 +297,16 @@ test.describe("TextField", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should hide the label when space-only text nodes are slotted", async () => {
await root.evaluate(node => {
test("should hide the label when space-only text nodes are slotted", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const label = element.locator(".label");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = `<fast-text-field>\n \n</fast-text-field>`;
});
@ -235,8 +315,16 @@ test.describe("TextField", () => {
await expect(label).toHaveClass(/label__hidden/);
});
test("should fire a `change` event when the internal control emits a `change` event", async () => {
await root.evaluate(node => {
test("should fire a `change` event when the internal control emits a `change` event", async ({
page,
}) => {
const element = page.locator("fast-text-field");
const control = element.locator(".control");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field></fast-text-field>
`;
@ -262,8 +350,14 @@ test.describe("TextField", () => {
});
test.describe("with a type of `password`", () => {
test("should report invalid validity when the `value` property is an empty string and `required` is true", async () => {
await root.evaluate(node => {
test("should report invalid validity when the `value` property is an empty string and `required` is true", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" required></fast-text-field>
`;
@ -276,8 +370,14 @@ test.describe("TextField", () => {
).toBeTruthy();
});
test("should report valid validity when the `value` property is a string that is non-empty and `required` is true", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` property is a string that is non-empty and `required` is true", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" required value="some-value"></fast-text-field>
`;
@ -290,8 +390,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` is empty and `minlength` is set", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is empty and `minlength` is set", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" minlength="1"></fast-text-field>
`;
@ -304,8 +410,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` has a length less than `minlength`", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` has a length less than `minlength`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" minlength="10" value="123456789"></fast-text-field>
`;
@ -318,8 +430,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` is empty and `maxlength` is set", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is empty and `maxlength` is set", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" maxlength="10"></fast-text-field>
`;
@ -332,8 +450,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` has a length which exceeds the `maxlength`", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` has a length which exceeds the `maxlength`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" maxlength="10" value="12345678901"></fast-text-field>
`;
@ -346,8 +470,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the `value` is shorter than `maxlength` and the element is `required`", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` is shorter than `maxlength` and the element is `required`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" maxlength="10" required value="123456789"></fast-text-field>
`;
@ -360,8 +490,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the `value` property matches the `pattern` property", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` property matches the `pattern` property", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" pattern="\\d+" value="123456789"></fast-text-field>
`;
@ -374,15 +510,19 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report invalid validity when `value` does not match `pattern`", async () => {
await root.evaluate(node => {
test("should report invalid validity when `value` does not match `pattern`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="password" pattern="value" value="other value"></fast-text-field>
`;
});
const element = page.locator("fast-text-field");
expect(
await element.evaluate<boolean, FASTTextField>(
node => node.validity.patternMismatch
@ -392,8 +532,14 @@ test.describe("TextField", () => {
});
test.describe("with a type of `tel`", () => {
test("should report invalid validity when the `value` property is an empty string and `required` is true", async () => {
await root.evaluate(node => {
test("should report invalid validity when the `value` property is an empty string and `required` is true", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" required></fast-text-field>
`;
@ -406,8 +552,14 @@ test.describe("TextField", () => {
).toBeTruthy();
});
test("should report valid validity when the `value` property is not empty and `required` is true", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` property is not empty and `required` is true", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" required value="some-value"></fast-text-field>
`;
@ -420,8 +572,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `minlength` is set and `value` is an empty string", async () => {
await root.evaluate(node => {
test("should report valid validity when `minlength` is set and `value` is an empty string", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" minlength="1"></fast-text-field>
`;
@ -434,8 +592,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the length of `value` is less than `minlength`", async () => {
await root.evaluate(node => {
test("should report valid validity when the length of `value` is less than `minlength`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" minlength="10" value="123456789"></fast-text-field>
`;
@ -448,8 +612,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` is an empty string", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is an empty string", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" maxlength="10"></fast-text-field>
`;
@ -462,8 +632,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` has a length which exceeds `maxlength`", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` has a length which exceeds `maxlength`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" maxlength="10" value="12345678901"></fast-text-field>
`;
@ -476,8 +652,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the `value` is shorter than `maxlength` and the element is `required`", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` is shorter than `maxlength` and the element is `required`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" maxlength="10" required value="123456789"></fast-text-field>
`;
@ -490,15 +672,19 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the `value` matches `pattern`", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` matches `pattern`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" pattern="\\d+" value="123456789"></fast-text-field>
`;
});
const element = page.locator("fast-text-field");
expect(
await element.evaluate<boolean, FASTTextField>(
node => node.validity.patternMismatch
@ -506,15 +692,19 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report invalid validity when `value` does not match `pattern`", async () => {
await root.evaluate(node => {
test("should report invalid validity when `value` does not match `pattern`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="tel" pattern="value" required value="other value"></fast-text-field>
`;
});
const element = page.locator("fast-text-field");
expect(
await element.evaluate<boolean, FASTTextField>(
node => node.validity.patternMismatch
@ -524,8 +714,14 @@ test.describe("TextField", () => {
});
test.describe("with a type of `text`", () => {
test("should report invalid validity when the `value` property is an empty string and `required` is true", async () => {
await root.evaluate(node => {
test("should report invalid validity when the `value` property is an empty string and `required` is true", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" required></fast-text-field>
`;
@ -538,8 +734,14 @@ test.describe("TextField", () => {
).toBeTruthy();
});
test("should report valid validity when the `value` property is not empty and `required` is true", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` property is not empty and `required` is true", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" required value="some value"></fast-text-field>
`;
@ -552,8 +754,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` is an empty string and `minlength` is set", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is an empty string and `minlength` is set", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" minlength="1"></fast-text-field>
`;
@ -566,8 +774,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` has a length less than `minlength`", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` has a length less than `minlength`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" minlength="6" value="value"></fast-text-field>
`;
@ -580,8 +794,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` is empty and `maxlength` is set", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is empty and `maxlength` is set", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" maxlength="0"></fast-text-field>
`;
@ -594,8 +814,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when `value` has a length which exceeds `maxlength`", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` has a length which exceeds `maxlength`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" maxlength="4" value="value"></fast-text-field>
`;
@ -608,8 +834,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the `value` is shorter than `maxlength` and the element is `required`", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` is shorter than `maxlength` and the element is `required`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" maxlength="6" required value="value"></fast-text-field>
`;
@ -622,8 +854,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report valid validity when the `value` matches `pattern`", async () => {
await root.evaluate(node => {
test("should report valid validity when the `value` matches `pattern`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" pattern="value" required value="value"></fast-text-field>
`;
@ -636,8 +874,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should report invalid validity when `value` does not match `pattern`", async () => {
await root.evaluate(node => {
test("should report invalid validity when `value` does not match `pattern`", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="text" pattern="value" required value="other value"></fast-text-field>
`;
@ -652,8 +896,14 @@ test.describe("TextField", () => {
});
test.describe("with a type of `email`", () => {
test("should report valid validity when `value` is an empty string", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is an empty string", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="email"></fast-text-field>
`;
@ -666,8 +916,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should have invalid invalidity with a `typeMismatch` when `value` is not a valid email", async () => {
await root.evaluate(node => {
test("should have invalid invalidity with a `typeMismatch` when `value` is not a valid email", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="email" value="not an email"></fast-text-field>
`;
@ -682,8 +938,14 @@ test.describe("TextField", () => {
});
test.describe("with a type of `url`", () => {
test("should report valid validity when `value` is an empty string", async () => {
await root.evaluate(node => {
test("should report valid validity when `value` is an empty string", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="url"></fast-text-field>
`;
@ -696,8 +958,14 @@ test.describe("TextField", () => {
).toBeFalsy();
});
test("should have invalid invalidity with a `typeMismatch` when `value` is not a valid URL", async () => {
await root.evaluate(node => {
test("should have invalid invalidity with a `typeMismatch` when `value` is not a valid URL", async ({
page,
}) => {
const element = page.locator("fast-text-field");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-text-field type="url" value="not a url"></fast-text-field>
`;

Просмотреть файл

@ -1,4 +1,10 @@
import { ElementViewTemplate, html, ref, slotted } from "@microsoft/fast-element";
import {
elements,
ElementViewTemplate,
html,
ref,
slotted,
} from "@microsoft/fast-element";
import { endSlotTemplate, startSlotTemplate } from "../patterns/index.js";
import { whitespaceFilter } from "../utilities/whitespace-filter.js";
import type { FASTTextField, TextFieldOptions } from "./text-field.js";
@ -11,6 +17,13 @@ export function textFieldTemplate<T extends FASTTextField>(
options: TextFieldOptions = {}
): ElementViewTemplate<T> {
return html<T>`
<slot
name="datalist"
${slotted({
property: "slottedDataList",
filter: elements("datalist"),
})}
></slot>
<label
part="label"
for="control"

Просмотреть файл

@ -4,6 +4,7 @@ import {
observable,
Updates,
} from "@microsoft/fast-element";
import { uniqueId } from "@microsoft/fast-web-utilities";
import { ARIAGlobalStatesAndProperties, StartEnd } from "../patterns/index.js";
import type { StartEndOptions } from "../patterns/start-end.js";
import { applyMixins } from "../utilities/apply-mixins.js";
@ -93,6 +94,30 @@ export class FASTTextField extends FormAssociatedTextField {
}
}
private dataList: HTMLDataListElement | undefined;
/**
* The slotted datalist elements.
* @public
*/
@observable
public slottedDataList?: HTMLDataListElement[];
protected slottedDataListChanged(
prev: HTMLDataListElement[] | undefined,
next: HTMLDataListElement[] | undefined
): void {
this.dataList = next?.[0];
if (!this.dataList) {
return;
}
if (!this.dataList.id) {
this.dataList.id = uniqueId("datalist-");
}
this.control.setAttribute("list", this.dataList.id);
}
/**
* Allows associating a {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist | datalist} to the element by {@link https://developer.mozilla.org/en-US/docs/Web/API/Element/id}.
* @public

Просмотреть файл

@ -1,36 +1,48 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import { ToolbarOrientation } from "./toolbar.options.js";
test.describe("Toolbar", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should have a role of `toolbar`", async ({ page }) => {
const element = page.locator("fast-toolbar");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-toolbar");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar></fast-toolbar>
`;
});
root = page.locator("#root");
await page.goto(fixtureURL("toolbar--toolbar"));
});
test("should have a role of `toolbar`", async () => {
await expect(element).toHaveAttribute("role", "toolbar");
});
test("should have a default orientation of `horizontal`", async () => {
test("should have a default orientation of `horizontal`", async ({ page }) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar></fast-toolbar>
`;
});
await expect(element).toHaveAttribute(
"orientation",
ToolbarOrientation.horizontal
);
});
test("should move focus to its first focusable element when it receives focus", async () => {
await root.evaluate(node => {
test("should move focus to its first focusable element when it receives focus", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
const buttons = element.locator("button");
const firstButton = buttons.filter({ hasText: "Start Slot Button" });
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button>Button 1</button>
@ -44,19 +56,20 @@ test.describe("Toolbar", () => {
`;
});
const element = page.locator("fast-toolbar");
const buttons = element.locator("button");
const firstButton = buttons.filter({ hasText: "Start Slot Button" });
await element.focus();
await expect(firstButton).toBeFocused();
});
test("should move focus to next element when keyboard incrementer is pressed", async () => {
await root.evaluate(node => {
test("should move focus to next element when keyboard incrementer is pressed", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
const buttons = element.locator("button");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -70,8 +83,6 @@ test.describe("Toolbar", () => {
`;
});
const buttons = element.locator("button");
await element.focus();
await expect(buttons.filter({ hasText: "Start Slot Button" })).toBeFocused();
@ -101,8 +112,15 @@ test.describe("Toolbar", () => {
await expect(buttons.filter({ hasText: "End Slot Button" })).toBeFocused();
});
test("should skip disabled elements when keyboard incrementer is pressed", async () => {
await root.evaluate(node => {
test("should skip disabled elements when keyboard incrementer is pressed", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
const buttons = element.locator("button:not([slot])");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -116,8 +134,6 @@ test.describe("Toolbar", () => {
`;
});
const buttons = element.locator("button:not([slot])");
const startSlotButton = element.locator("button", {
hasText: "Start Slot Button",
});
@ -147,8 +163,15 @@ test.describe("Toolbar", () => {
await expect(endSlotButton).toBeFocused();
});
test("should skip hidden elements when keyboard incrementer is pressed", async () => {
await root.evaluate(node => {
test("should skip hidden elements when keyboard incrementer is pressed", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
const buttons = element.locator("button:not([slot])");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -162,8 +185,6 @@ test.describe("Toolbar", () => {
`;
});
const buttons = element.locator("button:not([slot])");
const startSlotButton = element.locator("button", {
hasText: "Start Slot Button",
});
@ -193,8 +214,14 @@ test.describe("Toolbar", () => {
await expect(endSlotButton).toBeFocused();
});
test("should move focus to next element when keyboard incrementer is pressed and start slot content is added after connect", async () => {
await root.evaluate(node => {
test("should move focus to next element when keyboard incrementer is pressed and start slot content is added after connect", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `<fast-toolbar></fast-toolbar>`;
});
@ -225,8 +252,14 @@ test.describe("Toolbar", () => {
await expect(button2).toBeFocused();
});
test("should move focus to next element when keyboard incrementer is pressed and end slot content is added after connect", async () => {
await root.evaluate(node => {
test("should move focus to next element when keyboard incrementer is pressed and end slot content is added after connect", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar></fast-toolbar>
`;
@ -260,8 +293,14 @@ test.describe("Toolbar", () => {
await expect(endSlotButton).toBeFocused();
});
test("should maintain correct activeIndex when the set of focusable children changes", async () => {
await root.evaluate(node => {
test("should maintain correct activeIndex when the set of focusable children changes", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -304,8 +343,14 @@ test.describe("Toolbar", () => {
await expect(element).toHaveJSProperty("activeIndex", 1);
});
test("should reset activeIndex to 0 when the focused item is no longer focusable", async () => {
await root.evaluate(node => {
test("should reset activeIndex to 0 when the focused item is no longer focusable", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -350,8 +395,14 @@ test.describe("Toolbar", () => {
await expect(element).toHaveJSProperty("activeIndex", 0);
});
test("should update activeIndex when an element within the toolbar is clicked", async () => {
await root.evaluate(node => {
test("should update activeIndex when an element within the toolbar is clicked", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -382,8 +433,14 @@ test.describe("Toolbar", () => {
await expect(element).toHaveJSProperty("activeIndex", 2);
});
test("should update activeIndex when a nested element within the toolbar is clicked", async () => {
await root.evaluate(node => {
test("should update activeIndex when a nested element within the toolbar is clicked", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -419,8 +476,14 @@ test.describe("Toolbar", () => {
await expect(element).toHaveJSProperty("activeIndex", 2);
});
test("should focus most recently focused item when toolbar receives focus", async () => {
await root.evaluate(node => {
test("should focus most recently focused item when toolbar receives focus", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -436,7 +499,9 @@ test.describe("Toolbar", () => {
});
const button2 = element.locator("button", { hasText: "Button 2" });
const buttonOutsideToolbar = page.locator("button", { hasText: "Button Outside Toolbar"});
const buttonOutsideToolbar = page.locator("button", {
hasText: "Button Outside Toolbar",
});
await button2.click();
await expect(button2).toBeFocused();
@ -449,8 +514,14 @@ test.describe("Toolbar", () => {
await expect(button2).toBeFocused();
});
test("should focus clicked item when focus enters toolbar by clicking an item that is not the most recently focused item", async () => {
await root.evaluate(node => {
test("should focus clicked item when focus enters toolbar by clicking an item that is not the most recently focused item", async ({
page,
}) => {
const element = page.locator("fast-toolbar");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-toolbar>
<button slot="start">Start Slot Button</button>
@ -468,7 +539,9 @@ test.describe("Toolbar", () => {
const button3 = element.locator("button", { hasText: "Button 3" });
const buttonOutsideToolbar = page.locator("button", { hasText: "Button Outside Toolbar"});
const buttonOutsideToolbar = page.locator("button", {
hasText: "Button Outside Toolbar",
});
await button2.click();
await expect(button2).toBeFocused();

Просмотреть файл

@ -1,38 +1,30 @@
import type { Locator, Page } from "@playwright/test";
import { expect, test } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTTooltip } from "./tooltip.js";
test.describe("Tooltip", () => {
let page: Page;
let element: Locator;
let root: Locator;
let anchoredRegion: Locator;
test("should not render the tooltip by default", async ({ page }) => {
const element = page.locator("fast-tooltip");
const anchoredRegion = element.locator("fast-anchored-region");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-tooltip");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip></fast-tooltip>
`;
});
root = page.locator("#root");
anchoredRegion = element.locator("fast-anchored-region");
await page.goto(fixtureURL("tooltip--tooltip", { delay: 0 }));
});
test.afterAll(async () => {
await page.close();
});
test("should not render the tooltip by default", async () => {
await expect(element).toHaveJSProperty("visible", false);
await expect(anchoredRegion).toBeHidden();
});
test("should render the tooltip when the anchor is hovered", async () => {
await root.evaluate((node: HTMLElement) => {
test("should render the tooltip when the anchor is hovered", async ({ page }) => {
const element = page.locator("fast-tooltip");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip anchor="anchor-default">
Tooltip
@ -52,8 +44,12 @@ test.describe("Tooltip", () => {
await expect(element).toBeVisible();
});
test("should render the tooltip when the anchor is focused", async () => {
await root.evaluate((node: HTMLElement) => {
test("should render the tooltip when the anchor is focused", async ({ page }) => {
const element = page.locator("fast-tooltip");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip anchor="anchor-default">
Tooltip
@ -77,8 +73,14 @@ test.describe("Tooltip", () => {
await expect(element).toBeVisible();
});
test('should render the tooltip when the `show` attribute is "true"', async () => {
await root.evaluate((node: HTMLElement) => {
test('should render the tooltip when the `show` attribute is "true"', async ({
page,
}) => {
const element = page.locator("fast-tooltip");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip anchor="anchor-default" show="true">
Tooltip
@ -94,8 +96,14 @@ test.describe("Tooltip", () => {
await expect(element).toBeVisible();
});
test('should not render the tooltip when the `show` attribute is "false"', async () => {
await root.evaluate((node: HTMLElement) => {
test('should not render the tooltip when the `show` attribute is "false"', async ({
page,
}) => {
const element = page.locator("fast-tooltip");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip anchor="anchor-default" show="false">
Tooltip
@ -115,8 +123,14 @@ test.describe("Tooltip", () => {
await expect(element).toBeHidden();
});
test('should not render the tooltip when the anchor is hovered and `show` is "false"', async () => {
await root.evaluate((node: HTMLElement) => {
test('should not render the tooltip when the anchor is hovered and `show` is "false"', async ({
page,
}) => {
const element = page.locator("fast-tooltip");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip anchor="anchor-default">
Tooltip
@ -144,10 +158,23 @@ test.describe("Tooltip", () => {
await expect(element).toBeHidden();
});
test("should change anchor element when the `anchor` attribute changes", async () => {
await page.goto(fixtureURL("tooltip--tooltip"));
test("should change anchor element when the `anchor` attribute changes", async ({
page,
}) => {
const element = page.locator("fast-tooltip");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tooltip anchor="anchor-default">
Tooltip
</fast-tooltip>
<fast-button id="anchor-default">
Hover or focus me
</fast-button>
`;
await root.evaluate(node => {
const newAnchor = document.createElement("div");
newAnchor.id = "new-anchor";
@ -155,7 +182,9 @@ test.describe("Tooltip", () => {
});
expect(
await element.evaluate((node: FASTTooltip) => node.anchorElement?.id)
await element.evaluate<string | undefined, FASTTooltip>(
node => node.anchorElement?.id
)
).toBe("anchor-default");
await element.evaluate(node => {
@ -163,7 +192,9 @@ test.describe("Tooltip", () => {
});
expect(
await element.evaluate((node: FASTTooltip) => node.anchorElement?.id)
await element.evaluate<string | undefined, FASTTooltip>(
node => node.anchorElement?.id
)
).toBe("new-anchor");
});
});

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
import type { FASTTreeItem } from "./tree-item.js";
test.describe("TreeItem", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should include a role of `treeitem`", async ({ page }) => {
const element = page.locator("fast-tree-item");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-tree-item");
root = page.locator("#root");
await page.goto(fixtureURL("tree-view-tree-item--tree-item"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of `treeitem`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
@ -32,8 +16,14 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("role", "treeitem");
});
test("should set the `aria-expanded` attribute equal to the `expanded` value when the tree item has children", async () => {
await root.evaluate(node => {
test("should set the `aria-expanded` attribute equal to the `expanded` value when the tree item has children", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>
<fast-tree-item>tree item</fast-tree-item>
@ -54,8 +44,14 @@ test.describe("TreeItem", () => {
await expect(element.first()).toHaveAttribute("aria-expanded", "false");
});
test("should set the `aria-expanded` attribute equal to TRUE when the `expanded` attribute is present", async () => {
await root.evaluate(node => {
test("should set the `aria-expanded` attribute equal to TRUE when the `expanded` attribute is present", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item expanded>
<fast-tree-item>tree item</fast-tree-item>
@ -66,44 +62,68 @@ test.describe("TreeItem", () => {
await expect(element.first()).toHaveAttribute("aria-expanded", "true");
});
test("should NOT set the `aria-expanded` attribute when the tree item has no children", async () => {
await root.evaluate(node => {
test("should NOT set the `aria-expanded` attribute when the tree item has no children", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
});
await expect(element).not.hasAttribute("aria-expanded");
await expect(element).not.toHaveAttribute("aria-expanded");
});
test("should NOT set the `aria-expanded` attribute when the `expanded` state is true and the tree item has no children", async () => {
await root.evaluate(node => {
test("should NOT set the `aria-expanded` attribute when the `expanded` state is true and the tree item has no children", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item expanded></fast-tree-item>
`;
});
await expect(element).not.hasAttribute("aria-expanded");
await expect(element).not.toHaveAttribute("aria-expanded");
await element.evaluate<void, FASTTreeItem>(node => {
node.expanded = true;
});
await expect(element).not.hasAttribute("aria-expanded");
await expect(element).not.toHaveAttribute("aria-expanded");
});
test("should NOT set the `aria-selected` attribute if `selected` value is not provided", async () => {
await root.evaluate(node => {
test("should NOT set the `aria-selected` attribute if `selected` value is not provided", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
});
await expect(element).not.hasAttribute("aria-selected");
await expect(element).not.toHaveAttribute("aria-selected");
});
test("should set the `aria-selected` attribute equal to the `selected` value", async () => {
await root.evaluate(node => {
test("should set the `aria-selected` attribute equal to the `selected` value", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
@ -122,8 +142,14 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("aria-selected", "false");
});
test("should set the `aria-selected` attribute equal to TRUE when the selected attribute is present", async () => {
await root.evaluate(node => {
test("should set the `aria-selected` attribute equal to TRUE when the selected attribute is present", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item selected>tree item</fast-tree-item>
`;
@ -138,14 +164,20 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set the `aria-disabled` attribute equal to the `disabled` property", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to the `disabled` property", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
});
await expect(element).not.hasAttribute("aria-disabled");
await expect(element).not.toHaveAttribute("aria-disabled");
await element.evaluate<void, FASTTreeItem>(node => {
node.disabled = true;
@ -160,8 +192,14 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("aria-disabled", "false");
});
test("should set the `aria-disabled` attribute equal to TRUE when the disabled attribute is present", async () => {
await root.evaluate(node => {
test("should set the `aria-disabled` attribute equal to TRUE when the disabled attribute is present", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item disabled>tree item</fast-tree-item>
`;
@ -170,8 +208,14 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("aria-disabled", "true");
});
test('should add a slot attribute of "item" to nested tree items', async () => {
await root.evaluate(node => {
test('should add a slot attribute of "item" to nested tree items', async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>
<fast-tree-item>tree item</fast-tree-item>
@ -185,8 +229,12 @@ test.describe("TreeItem", () => {
);
});
test("should have a default `tabindex` attribute of -1", async () => {
await root.evaluate(node => {
test("should have a default `tabindex` attribute of -1", async ({ page }) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
@ -195,8 +243,12 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("tabindex", "-1");
});
test("should have a tabindex of 0 when focused", async () => {
await root.evaluate(node => {
test("should have a tabindex of 0 when focused", async ({ page }) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
@ -207,8 +259,14 @@ test.describe("TreeItem", () => {
await expect(element).toHaveAttribute("tabindex", "0");
});
test("should render an element with a class of `expand-collapse-button` when nested tree items exist", async () => {
await root.evaluate(node => {
test("should render an element with a class of `expand-collapse-button` when nested tree items exist", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>
<fast-tree-item>tree item</fast-tree-item>
@ -223,8 +281,14 @@ test.describe("TreeItem", () => {
await expect(expandCollapseButton).toHaveAttribute("aria-hidden", "true");
});
test("should render an element with a role of `group` when nested tree items exist and `expanded` is true", async () => {
await root.evaluate(node => {
test("should render an element with a role of `group` when nested tree items exist and `expanded` is true", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item expanded>
<fast-tree-item>tree item</fast-tree-item>
@ -235,8 +299,14 @@ test.describe("TreeItem", () => {
await expect(element.first().locator(".items")).toHaveAttribute("role", "group");
});
test("should NOT render an element with a role of `group` when nested tree items exist and `expanded` is false", async () => {
await root.evaluate(node => {
test("should NOT render an element with a role of `group` when nested tree items exist and `expanded` is false", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>
<fast-tree-item>tree item</fast-tree-item>
@ -248,8 +318,14 @@ test.describe("TreeItem", () => {
});
test.describe("events", () => {
test('should emit a "change" event when the `expanded` property changes', async () => {
await root.evaluate(node => {
test('should emit a "change" event when the `expanded` property changes', async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>
<fast-tree-item>tree item</fast-tree-item>
@ -280,8 +356,14 @@ test.describe("TreeItem", () => {
expect(wasClicked).toBe(true);
});
test("should fire a selected change event when the `selected` property changes", async () => {
await root.evaluate(node => {
test("should fire a selected change event when the `selected` property changes", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
@ -303,8 +385,14 @@ test.describe("TreeItem", () => {
expect(wasChanged).toBeTruthy();
});
test("should NOT set `selected` state when a disabled element is clicked", async () => {
await root.evaluate(node => {
test("should NOT set `selected` state when a disabled element is clicked", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item disabled>tree item</fast-tree-item>
`;
@ -314,11 +402,17 @@ test.describe("TreeItem", () => {
await expect(element).not.toHaveBooleanAttribute("selected");
await expect(element).not.hasAttribute("aria-selected");
await expect(element).not.toHaveAttribute("aria-selected");
});
test("should fire an event when expanded state changes via the `expanded` attribute", async () => {
await root.evaluate(node => {
test("should fire an event when expanded state changes via the `expanded` attribute", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;
@ -340,8 +434,14 @@ test.describe("TreeItem", () => {
expect(wasChanged).toBeTruthy();
});
test("should fire an event when selected state changes via the `selected` attribute", async () => {
await root.evaluate(node => {
test("should fire an event when selected state changes via the `selected` attribute", async ({
page,
}) => {
const element = page.locator("fast-tree-item");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-item>tree item</fast-tree-item>
`;

Просмотреть файл

@ -1,29 +1,13 @@
import { expect, test } from "@playwright/test";
import type { Locator, Page } from "@playwright/test";
import { fixtureURL } from "../__test__/helpers.js";
// TODO: Need to add tests for keyboard handling & focus management
test.describe("TreeView", () => {
let page: Page;
let element: Locator;
let root: Locator;
test("should include a role of `tree`", async ({ page }) => {
const element = page.locator("fast-tree-view");
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto("http://localhost:6006");
element = page.locator("fast-tree-view");
root = page.locator("#root");
await page.goto(fixtureURL("tree-view--tree-view"));
});
test.afterAll(async () => {
await page.close();
});
test("should include a role of `tree`", async () => {
await root.evaluate(node => {
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-view></fast-tree-view>
`;
@ -32,8 +16,14 @@ test.describe("TreeView", () => {
await expect(element).toHaveAttribute("role", "tree");
});
test("should set tree item `nested` properties to true if *any* tree item has nested items", async () => {
await root.evaluate(node => {
test("should set tree item `nested` properties to true if *any* tree item has nested items", async ({
page,
}) => {
const element = page.locator("fast-tree-view");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-view>
<fast-tree-item>
@ -66,8 +56,14 @@ test.describe("TreeView", () => {
).toBe(true);
});
test("should set the selected state on tree items when a tree item is clicked", async () => {
await root.evaluate(node => {
test("should set the selected state on tree items when a tree item is clicked", async ({
page,
}) => {
const element = page.locator("fast-tree-view");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-view>
<fast-tree-item>
@ -98,8 +94,12 @@ test.describe("TreeView", () => {
await expect(firstTreeItem).toHaveAttribute("aria-selected", "true");
});
test("should only allow one tree item to be selected at a time", async () => {
await root.evaluate(node => {
test("should only allow one tree item to be selected at a time", async ({ page }) => {
const element = page.locator("fast-tree-view");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-view>
<fast-tree-item>
@ -138,8 +138,14 @@ test.describe("TreeView", () => {
}
});
test("should toggle the expanded state when `expand-collapse-button` is clicked", async () => {
await root.evaluate(node => {
test("should toggle the expanded state when `expand-collapse-button` is clicked", async ({
page,
}) => {
const element = page.locator("fast-tree-view");
await page.goto("http://localhost:6006");
await page.locator("#root").evaluate(node => {
node.innerHTML = /* html */ `
<fast-tree-view>
<fast-tree-item>

Просмотреть файл

@ -0,0 +1 @@
<svg class="checked-indicator" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="m8.14 12.67 7.1-8.17 1.56 1.4-8.56 9.87-4.44-4.51 1.48-1.5 2.86 2.91Z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 213 B

Просмотреть файл

@ -0,0 +1,25 @@
# FAST Foundation Test Harness
This directory contains the test harness for FAST Foundation, which is a static webpage hosted by a small express server. The page provides a way to run the FAST Foundation tests in a browser, by importing each component definition and registering it with the browser's custom element registry. This is the same method used by the FAST Foundation Storybook.
## Structure
```text
test/
├── public/ # static assets for the test harness
│ ├── dist/ # gitignored build output directory
│ │ └── bundle.js # generated test harness bundle
│ └── index.html # test harness HTML file
├── bundle.ts # entry point for the test harness bundle
└── server.js # test harness express server
```
## Building the test harness bundle
The bundle is built along with the main package bundles via the `build:rollup` script in `package.json`. The Rollup config outputs the bundle to `public/dist/bundle.js`.
## Running the test harness
The Playwright config uses its `webServer` property to specify the command to start the test harness. While Playwright is running, the test harness will be available at `http://localhost:6007`.
If you'd like to run the test harness manually, you can run `node test/server.js` from the `fast-foundation` directory. This will start the test harness server separately from Playwright, which can be useful for debugging.

Просмотреть файл

@ -1,12 +1,13 @@
import { useArgs } from "@storybook/client-api";
import "@microsoft/fast-element";
import "../src/anchor/stories/anchor.register.js";
import "../src/anchor/stories/anchor.stories.js";
import "../src/anchored-region/stories/anchored-region.register.js";
import "../src/avatar/stories/avatar.register.js";
import "../src/badge/stories/badge.register.js";
import "../src/button/stories/button.register.js";
import "../src/card/stories/card.register.js";
import "../src/checkbox/stories/checkbox.register.js";
import "../src/design-token/stories/design-token.register.js";
import "../src/dialog/stories/dialog.register.js";
import "../src/disclosure/stories/disclosure.register.js";
import "../src/divider/stories/divider.register.js";
@ -59,13 +60,6 @@ import "../src/menu/stories/menu.register.js";
import "../src/tree-item/stories/tree-item.register.js";
import "../src/tree-view/stories/tree-view.register.js";
// Attach the FAST html helper to the global object so tests can use it
import { FAST, html } from "@microsoft/fast-element";
FAST.html = html;
export const decorators = [
(Story, context) => {
const [_, updateArgs] = useArgs();
return Story({ ...context, updateArgs });
},
];
FAST["html"] = html;

Просмотреть файл

Просмотреть файл

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Test Harness</title>
<style>
:root {
--accent-color: #da1a5f;
--accent-fill-active: #df3c77;
--accent-fill-focus: #dc2969;
--accent-fill-hover: #da1a5f;
--accent-fill-rest: #dc2969;
--accent-foreground-active: #dc2969;
--accent-foreground-focus: #da1a5f;
--accent-foreground-hover: #b3154e;
--accent-foreground-rest: #da1a5f;
--ambient-shadow-alpha: calc(0.11 * (2 - var(--background-luminance, 1)));
--ambient-shadow-color: rgba(0, 0, 0, var(--ambient-shadow-alpha));
--ambient-shadow: 0 0 calc((var(--elevation) * 0.225px) + 2px)
var(--ambient-shadow-color);
--base-height-multiplier: 10;
--base-horizontal-spacing-multiplier: 3;
--base-layer-luminance: 1;
--body-font: Arial, Helvetica, sans-serif;
--clear-button-active: #ededed;
--clear-button-hover: #f2f2f2;
--control-corner-radius: 4;
--density: 0;
--design-unit: 4;
--direction: ltr;
--directional-shadow: 0 calc(var(--elevation) * 0.4px)
calc((var(--elevation) * 0.9px)) var(--directional-shadow-color);
--directional-shadow-alpha: calc(
0.13 * (2 - var(--background-luminance, 1))
);
--directional-shadow-color: rgba(
0,
0,
0,
var(--directional-shadow-alpha)
);
--disabled-opacity: 0.3;
--elevation: 14;
--elevation-shadow: var(--ambient-shadow), var(--directional-shadow);
--height-number: (var(--base-height-multiplier) + var(--density)) *
var(--design-unit);
--fill-color: #ffffff;
--focus-stroke-inner: #ffffff;
--focus-stroke-outer: #888888;
--focus-stroke-width: 2;
--foreground-on-accent-active-large: #000000;
--foreground-on-accent-active: #000000;
--foreground-on-accent-focus-large: #000000;
--foreground-on-accent-focus: #ffffff;
--foreground-on-accent-hover-large: #000000;
--foreground-on-accent-hover: #ffffff;
--foreground-on-accent-rest-large: #000000;
--foreground-on-accent-rest: #ffffff;
--neutral-color: #808080;
--neutral-fill-active: #f2f2f2;
--neutral-fill-focus: #ffffff;
--neutral-fill-hover: #e5e5e5;
--neutral-fill-input-active: #ffffff;
--neutral-fill-input-focus: #ffffff;
--neutral-fill-input-hover: #ffffff;
--neutral-fill-input-rest: #ffffff;
--neutral-fill-layer-rest: #f7f7f7;
--neutral-fill-rest: #ededed;
--neutral-fill-stealth-active: #f7f7f7;
--neutral-fill-stealth-focus: #ffffff;
--neutral-fill-stealth-hover: #f2f2f2;
--neutral-fill-stealth-rest: #ffffff;
--neutral-fill-strong-active: #838383;
--neutral-fill-strong-focus: #767676;
--neutral-fill-strong-hover: #616161;
--neutral-fill-strong-rest: #767676;
--neutral-foreground-hint: #767676;
--neutral-foreground-rest: #2b2b2b;
--neutral-layer-1: #ffffff;
--neutral-layer-2: #e5e5e5;
--neutral-layer-3: #dddddd;
--neutral-layer-4: #d6d6d6;
--neutral-layer-card-container: #f7f7f7;
--neutral-layer-floating: #ffffff;
--neutral-stroke-active: #d6d6d6;
--neutral-stroke-divider-rest: #eaeaea;
--neutral-stroke-focus: #bebebe;
--neutral-stroke-hover: #979797;
--neutral-stroke-rest: #bebebe;
--stroke-width: 1;
--tree-item-expand-collapse-hover: #e5e5e5;
--tree-item-expand-collapse-selected-hover: #e0e0e0;
--type-ramp-base-font-size: 14px;
--type-ramp-base-line-height: 20px;
--type-ramp-minus-1-font-size: 12px;
--type-ramp-minus-1-line-height: 16px;
--type-ramp-minus-2-font-size: 10px;
--type-ramp-minus-2-line-height: 16px;
--type-ramp-plus-1-font-size: 16px;
--type-ramp-plus-1-line-height: 24px;
--type-ramp-plus-2-font-size: 20px;
--type-ramp-plus-2-line-height: 28px;
--type-ramp-plus-3-font-size: 28px;
--type-ramp-plus-3-line-height: 36px;
--type-ramp-plus-4-font-size: 34px;
--type-ramp-plus-4-line-height: 44px;
--type-ramp-plus-5-font-size: 46px;
--type-ramp-plus-5-line-height: 56px;
--type-ramp-plus-6-font-size: 60px;
--type-ramp-plus-6-line-height: 72px;
}
html {
background-color: var(--fill-color);
color: var(--neutral-foreground-rest);
min-height: 100%;
}
</style>
</head>
<body>
<svg style="display: none">
<symbol id="test-icon" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
d="M12 5.5a1 1 0 100 2 1 1 0 000-2zm-5 1a1 1 0 112 0 1 1 0 01-2 0zm3.5-4a.5.5 0 00-1 0V3h-3C5.67 3 5 3.67 5 4.5v4c0 .83.67 1.5 1.5 1.5h7c.83 0 1.5-.67 1.5-1.5v-4c0-.83-.67-1.5-1.5-1.5h-3v-.5zM6.5 4h7c.28 0 .5.22.5.5v4a.5.5 0 01-.5.5h-7a.5.5 0 01-.5-.5v-4c0-.28.22-.5.5-.5zm3.75 14c2.62-.04 4.2-.6 5.12-1.44A3.52 3.52 0 0016.5 14h.01v-.69c0-1-.81-1.8-1.8-1.8h-3.2v-.01H5.3c-.99 0-1.8.81-1.8 1.81v.7c.04.77.25 1.75 1.13 2.55.93.84 2.5 1.4 5.12 1.44h.5zm-4.94-5.5h9.38c.45 0 .81.37.81.81v.44c0 .69-.13 1.46-.8 2.07C14 16.45 12.66 17 10 17s-4.01-.55-4.7-1.18a2.63 2.63 0 01-.8-2.07v-.44c0-.44.36-.8.8-.8z"
/>
</symbol>
<symbol
id="test-icon-2"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.26 4.6a5.21 5.21 0 0 1 9.03 5.22l-.2.34a.5.5 0 0 1-.67.19l-3.47-2-1.93 3.38c1.34.4 2.5 1.33 3.31 2.52h-.09c-.34 0-.66.11-.92.31A4.9 4.9 0 0 0 9.5 12.5a4.9 4.9 0 0 0-3.82 2.06 1.5 1.5 0 0 0-1.01-.3 5.94 5.94 0 0 1 5.31-2.74l2.1-3.68-3.83-2.2a.5.5 0 0 1-.18-.7l.2-.33Zm.92.42 1.7.98.02-.02a8.08 8.08 0 0 1 3.27-2.74 4.22 4.22 0 0 0-4.99 1.78ZM14 7.8c.47-.82.7-1.46.77-2.09a5.8 5.8 0 0 0-.06-1.62 6.96 6.96 0 0 0-2.95 2.41L14 7.8Zm.87.5 1.61.93a4.22 4.22 0 0 0-.74-5.02c.07.56.09 1.1.02 1.63-.1.79-.38 1.56-.89 2.46Zm-9.63 7.3a.5.5 0 0 0-.96.03c-.17.7-.5 1.08-.86 1.3-.38.23-.87.32-1.42.32a.5.5 0 0 0 0 1c.64 0 1.33-.1 1.94-.47.34-.2.64-.5.88-.87a2.96 2.96 0 0 0 4.68-.01 2.96 2.96 0 0 0 4.74-.06c.64.9 1.7 1.41 2.76 1.41a.5.5 0 1 0 0-1c-.98 0-1.96-.64-2.29-1.65a.5.5 0 0 0-.95 0 1.98 1.98 0 0 1-3.79.07.5.5 0 0 0-.94 0 1.98 1.98 0 0 1-3.8-.08Z"
/>
</symbol>
<symbol
id="chevron-up-12-regular"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.15 7.35c.2.2.5.2.7 0L6 4.21l3.15 3.14a.5.5 0 1 0 .7-.7l-3.5-3.5a.5.5 0 0 0-.7 0l-3.5 3.5a.5.5 0 0 0 0 .7Z"
/>
</symbol>
</svg>
<div id="root"></div>
<script src="dist/bundle.js"></script>
</body>
</html>

Просмотреть файл

@ -0,0 +1,12 @@
/* eslint-env node */
import express from "express";
const PORT = process.env.PORT ?? 6007;
const app = express();
// host public folder
app.use(express.static("test/public"));
// run server
app.listen(PORT);

Просмотреть файл

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": false,
"declarationDir": null,
"outDir": "test/dist",
"module": "ESNext"
},
"include": ["src/**/*.register.ts", "src/__test__/**/*.ts", "test/**/*.ts"]
}

8452
yarn.lock

Разница между файлами не показана из-за своего большого размера Загрузить разницу