@ngneers/controls-playwright 0.0.1-pre1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -0
- package/awesome-publish.config.ts +6 -0
- package/dist/components/accordion.d.ts +21 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +42 -0
- package/dist/components/avatar.d.ts +9 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +16 -0
- package/dist/components/breadcrumb.d.ts +20 -0
- package/dist/components/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb.js +15 -0
- package/dist/components/button-group.d.ts +10 -0
- package/dist/components/button-group.d.ts.map +1 -0
- package/dist/components/button-group.js +16 -0
- package/dist/components/button.d.ts +6 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +6 -0
- package/dist/components/calendar.d.ts +101 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +56 -0
- package/dist/components/chip.d.ts +15 -0
- package/dist/components/chip.d.ts.map +1 -0
- package/dist/components/chip.js +38 -0
- package/dist/components/edit-inplace.d.ts +18 -0
- package/dist/components/edit-inplace.d.ts.map +1 -0
- package/dist/components/edit-inplace.js +29 -0
- package/dist/components/filter.d.ts +186 -0
- package/dist/components/filter.d.ts.map +1 -0
- package/dist/components/filter.js +55 -0
- package/dist/components/hint.d.ts +10 -0
- package/dist/components/hint.d.ts.map +1 -0
- package/dist/components/hint.js +22 -0
- package/dist/components/index.d.ts +38 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +37 -0
- package/dist/components/inplace.d.ts +12 -0
- package/dist/components/inplace.d.ts.map +1 -0
- package/dist/components/inplace.js +29 -0
- package/dist/components/input-field.d.ts +12 -0
- package/dist/components/input-field.d.ts.map +1 -0
- package/dist/components/input-field.js +11 -0
- package/dist/components/input.d.ts +12 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +25 -0
- package/dist/components/item-view.d.ts +18 -0
- package/dist/components/item-view.d.ts.map +1 -0
- package/dist/components/item-view.js +39 -0
- package/dist/components/list-box.d.ts +20 -0
- package/dist/components/list-box.d.ts.map +1 -0
- package/dist/components/list-box.js +24 -0
- package/dist/components/mask-input.d.ts +62 -0
- package/dist/components/mask-input.d.ts.map +1 -0
- package/dist/components/mask-input.js +109 -0
- package/dist/components/menu.d.ts +16 -0
- package/dist/components/menu.d.ts.map +1 -0
- package/dist/components/menu.js +26 -0
- package/dist/components/message.d.ts +10 -0
- package/dist/components/message.d.ts.map +1 -0
- package/dist/components/message.js +22 -0
- package/dist/components/popover.d.ts +12 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +33 -0
- package/dist/components/progress.d.ts +17 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +46 -0
- package/dist/components/radio.d.ts +16 -0
- package/dist/components/radio.d.ts.map +1 -0
- package/dist/components/radio.js +35 -0
- package/dist/components/scroller.d.ts +20 -0
- package/dist/components/scroller.d.ts.map +1 -0
- package/dist/components/scroller.js +58 -0
- package/dist/components/select-button.d.ts +30 -0
- package/dist/components/select-button.d.ts.map +1 -0
- package/dist/components/select-button.js +46 -0
- package/dist/components/select.d.ts +46 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +74 -0
- package/dist/components/slider.d.ts +26 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +73 -0
- package/dist/components/snackbar.d.ts +27 -0
- package/dist/components/snackbar.d.ts.map +1 -0
- package/dist/components/snackbar.js +70 -0
- package/dist/components/spinner.d.ts +11 -0
- package/dist/components/spinner.d.ts.map +1 -0
- package/dist/components/spinner.js +22 -0
- package/dist/components/state.d.ts +31 -0
- package/dist/components/state.d.ts.map +1 -0
- package/dist/components/state.js +31 -0
- package/dist/components/switch.d.ts +12 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +39 -0
- package/dist/components/tabs.d.ts +22 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +42 -0
- package/dist/components/tag.d.ts +10 -0
- package/dist/components/tag.d.ts.map +1 -0
- package/dist/components/tag.js +22 -0
- package/dist/components/toast.d.ts +27 -0
- package/dist/components/toast.d.ts.map +1 -0
- package/dist/components/toast.js +68 -0
- package/dist/components/toggle-button.d.ts +10 -0
- package/dist/components/toggle-button.d.ts.map +1 -0
- package/dist/components/toggle-button.js +29 -0
- package/dist/components/tooltip.d.ts +12 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +32 -0
- package/dist/components/tree.d.ts +47 -0
- package/dist/components/tree.d.ts.map +1 -0
- package/dist/components/tree.js +85 -0
- package/dist/components/upload.d.ts +27 -0
- package/dist/components/upload.d.ts.map +1 -0
- package/dist/components/upload.js +47 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/classes.d.ts +119 -0
- package/dist/utils/classes.d.ts.map +1 -0
- package/dist/utils/classes.js +31 -0
- package/dist/utils/theme.d.ts +3 -0
- package/dist/utils/theme.d.ts.map +1 -0
- package/dist/utils/theme.js +41 -0
- package/package.json +38 -0
- package/src/components/accordion.ts +50 -0
- package/src/components/avatar.ts +17 -0
- package/src/components/breadcrumb.ts +17 -0
- package/src/components/button-group.ts +18 -0
- package/src/components/button.ts +5 -0
- package/src/components/calendar.ts +67 -0
- package/src/components/chip.ts +43 -0
- package/src/components/edit-inplace.ts +34 -0
- package/src/components/filter.ts +70 -0
- package/src/components/hint.ts +25 -0
- package/src/components/index.ts +37 -0
- package/src/components/inplace.ts +33 -0
- package/src/components/input-field.ts +16 -0
- package/src/components/input.ts +32 -0
- package/src/components/item-view.ts +48 -0
- package/src/components/list-box.ts +25 -0
- package/src/components/mask-input.ts +119 -0
- package/src/components/menu.ts +29 -0
- package/src/components/message.ts +25 -0
- package/src/components/popover.ts +39 -0
- package/src/components/progress.ts +53 -0
- package/src/components/radio.ts +42 -0
- package/src/components/scroller.ts +68 -0
- package/src/components/select-button.ts +57 -0
- package/src/components/select.ts +81 -0
- package/src/components/slider.ts +84 -0
- package/src/components/snackbar.ts +84 -0
- package/src/components/spinner.ts +27 -0
- package/src/components/state.ts +37 -0
- package/src/components/switch.ts +43 -0
- package/src/components/tabs.ts +50 -0
- package/src/components/tag.ts +25 -0
- package/src/components/toast.ts +82 -0
- package/src/components/toggle-button.ts +33 -0
- package/src/components/tooltip.ts +38 -0
- package/src/components/tree.ts +98 -0
- package/src/components/upload.ts +63 -0
- package/src/index.ts +1 -0
- package/src/utils/classes.ts +32 -0
- package/src/utils/theme.ts +44 -0
- package/tsconfig.lib.json +20 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spinnerControlTemplate } from '@ngneers/controls-themes/templates/spinner';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import test, { expect, type Locator } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export class NgnSpinnerHarness {
|
|
6
|
+
public readonly classes = themeClasses(spinnerControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly locator: Locator;
|
|
9
|
+
public readonly svg: Locator;
|
|
10
|
+
public readonly circle: Locator;
|
|
11
|
+
|
|
12
|
+
constructor(locator: Locator) {
|
|
13
|
+
this.locator = locator;
|
|
14
|
+
this.svg = locator.locator(this.classes.svg);
|
|
15
|
+
this.circle = locator.locator(this.classes.circle);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async expectVisible() {
|
|
19
|
+
await expect(this.locator).toBeVisible();
|
|
20
|
+
await expect(this.svg).toBeVisible();
|
|
21
|
+
await expect(this.circle).toBeVisible();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async expectRole() {
|
|
25
|
+
await expect(this.locator).toHaveAttribute('role', 'status');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { stateControlTemplate } from '@ngneers/controls-themes/templates/state';
|
|
2
|
+
import { expect, type Locator } from '@playwright/test';
|
|
3
|
+
import { themeClasses } from '../utils/theme';
|
|
4
|
+
import { NgnSpinnerHarness } from './spinner';
|
|
5
|
+
|
|
6
|
+
export class NgnStateHarness {
|
|
7
|
+
public readonly classes = themeClasses(stateControlTemplate);
|
|
8
|
+
|
|
9
|
+
public readonly indicator: Locator;
|
|
10
|
+
public readonly icon: Locator;
|
|
11
|
+
public readonly spinner: NgnSpinnerHarness;
|
|
12
|
+
|
|
13
|
+
constructor(public readonly locator: Locator) {
|
|
14
|
+
this.indicator = locator.locator(this.classes.indicator);
|
|
15
|
+
this.icon = locator.locator('ngn-icon');
|
|
16
|
+
this.spinner = new NgnSpinnerHarness(locator.locator('ngn-spinner'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async expectLoading() {
|
|
20
|
+
await expect(this.locator).toBeVisible();
|
|
21
|
+
await this.spinner.expectVisible();
|
|
22
|
+
await expect(this.icon).toHaveCount(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async expectIcon(kind: string) {
|
|
26
|
+
await expect(this.locator).toBeVisible();
|
|
27
|
+
await expect(this.locator).toHaveClass(
|
|
28
|
+
new RegExp(`(^|\\s)ngn-state-kind-${escapeRegExp(kind)}(\\s|$)`)
|
|
29
|
+
);
|
|
30
|
+
await expect(this.icon).toBeVisible();
|
|
31
|
+
await expect(this.spinner.locator).toHaveCount(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function escapeRegExp(value: string): string {
|
|
36
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
37
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Locator, expect } from '@playwright/test';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import { switchControlTemplate } from '@ngneers/controls-themes/templates/switch';
|
|
4
|
+
|
|
5
|
+
export class NgnSwitchHarness {
|
|
6
|
+
public readonly classes = themeClasses(switchControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly locator: Locator;
|
|
9
|
+
public readonly input: Locator;
|
|
10
|
+
|
|
11
|
+
constructor(locator: Locator) {
|
|
12
|
+
this.locator = locator;
|
|
13
|
+
this.input = locator.locator(this.classes.input);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public async toggle(force = false) {
|
|
17
|
+
await this.input.click({ force });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public async expectValue(value: boolean) {
|
|
21
|
+
if (value) {
|
|
22
|
+
await expect(this.input).toBeChecked();
|
|
23
|
+
} else {
|
|
24
|
+
await expect(this.input).not.toBeChecked();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async expectDisabled(disabled: boolean) {
|
|
29
|
+
if (disabled) {
|
|
30
|
+
await expect(this.input).toBeDisabled();
|
|
31
|
+
} else {
|
|
32
|
+
await expect(this.input).not.toBeDisabled();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async expectReadonly(readonly: boolean) {
|
|
37
|
+
if (readonly) {
|
|
38
|
+
await expect(this.input).toHaveAttribute('aria-readonly');
|
|
39
|
+
} else {
|
|
40
|
+
await expect(this.input).not.toHaveAttribute('aria-readonly');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type Locator, expect } from '@playwright/test';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import { tabsControlTemplate } from '@ngneers/controls-themes/templates/tabs';
|
|
4
|
+
|
|
5
|
+
export class NgnTabsHarness {
|
|
6
|
+
public readonly classes = themeClasses(tabsControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly headers: Locator;
|
|
9
|
+
public readonly contents: Locator;
|
|
10
|
+
|
|
11
|
+
constructor(public locator: Locator) {
|
|
12
|
+
this.headers = this.locator.locator(this.classes['header']);
|
|
13
|
+
this.contents = this.locator.locator(this.classes['content']);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public getTabByIndex(index: number): NgnTabHarness {
|
|
17
|
+
return new NgnTabHarness(this, index);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public expectTabCount(count: number) {
|
|
21
|
+
return expect(this.headers).toHaveCount(count);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class NgnTabHarness {
|
|
26
|
+
public readonly classes = themeClasses(tabsControlTemplate);
|
|
27
|
+
|
|
28
|
+
public readonly header: Locator;
|
|
29
|
+
public readonly content: Locator;
|
|
30
|
+
|
|
31
|
+
constructor(
|
|
32
|
+
public tabs: NgnTabsHarness,
|
|
33
|
+
public index: number
|
|
34
|
+
) {
|
|
35
|
+
this.header = this.tabs.headers.nth(index);
|
|
36
|
+
this.content = this.tabs.contents.nth(index);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public expectHeaderText(expected: string) {
|
|
40
|
+
return expect(this.header).toHaveText(expected);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public expectActive(active = true) {
|
|
44
|
+
return expect(this.header).toHaveAttribute('aria-selected', String(active));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async select() {
|
|
48
|
+
await this.header.click();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { tagControlTemplate } from '@ngneers/controls-themes/templates/tag';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import test, { expect, type Locator } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export class NgnTagHarness {
|
|
6
|
+
public readonly classes = themeClasses(tagControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly locator: Locator;
|
|
9
|
+
public readonly content: Locator;
|
|
10
|
+
public readonly icon: Locator;
|
|
11
|
+
|
|
12
|
+
constructor(locator: Locator) {
|
|
13
|
+
this.locator = locator;
|
|
14
|
+
this.content = locator.locator(this.classes.content);
|
|
15
|
+
this.icon = locator.locator(this.classes.icon);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async expectIcon(hasIcon: boolean) {
|
|
19
|
+
if (hasIcon) {
|
|
20
|
+
await expect(this.icon).toBeVisible();
|
|
21
|
+
} else {
|
|
22
|
+
await expect(this.icon).not.toBeVisible();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { toastControlTemplate } from '@ngneers/controls-themes/templates/toast';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import test, { expect, type Locator, type Page } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export class NgnToastHarness {
|
|
6
|
+
public readonly classes = themeClasses(toastControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly locator: Locator;
|
|
9
|
+
public readonly header: Locator;
|
|
10
|
+
public readonly content: Locator;
|
|
11
|
+
public readonly icon: Locator;
|
|
12
|
+
public readonly closeButton: Locator;
|
|
13
|
+
|
|
14
|
+
constructor(locator: Locator) {
|
|
15
|
+
this.locator = locator;
|
|
16
|
+
this.header = locator.locator(this.classes.defaultHeader);
|
|
17
|
+
this.content = locator.locator(this.classes.defaultContent);
|
|
18
|
+
this.icon = this.header.locator('ngn-icon').first();
|
|
19
|
+
this.closeButton = this.header.locator('button');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async expectVisible() {
|
|
23
|
+
await expect(this.locator).toBeVisible();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async expectHidden() {
|
|
27
|
+
await expect(this.locator).not.toBeVisible();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async expectHeader(text: string) {
|
|
31
|
+
await expect(this.header).toContainText(text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async expectContent(text: string) {
|
|
35
|
+
await expect(this.content).toContainText(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public async expectIcon(hasIcon: boolean) {
|
|
39
|
+
if (hasIcon) {
|
|
40
|
+
await expect(this.icon).toBeVisible();
|
|
41
|
+
} else {
|
|
42
|
+
await expect(this.icon).not.toBeVisible();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async expectClosable(closable: boolean) {
|
|
47
|
+
if (closable) {
|
|
48
|
+
await expect(this.closeButton).toBeVisible();
|
|
49
|
+
} else {
|
|
50
|
+
await expect(this.closeButton).not.toBeVisible();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async close() {
|
|
55
|
+
await this.closeButton.click();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export class NgnToastHostHarness {
|
|
60
|
+
public readonly classes = themeClasses(toastControlTemplate);
|
|
61
|
+
public readonly locator: Locator;
|
|
62
|
+
|
|
63
|
+
constructor(page: Page) {
|
|
64
|
+
this.locator = page.locator('ngn-toast-host');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public getToast(index: number = 0): NgnToastHarness {
|
|
68
|
+
return new NgnToastHarness(this.locator.locator('ngn-toast').nth(index));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public getAllToasts(): Locator {
|
|
72
|
+
return this.locator.locator('ngn-toast');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public async expectToastCount(count: number) {
|
|
76
|
+
await expect(this.getAllToasts()).toHaveCount(count);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public async expectVisible() {
|
|
80
|
+
await expect(this.locator).toBeVisible();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Locator, expect } from '@playwright/test';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import { toggleButtonControlTemplate } from '@ngneers/controls-themes/templates/toggle-button';
|
|
4
|
+
|
|
5
|
+
export class NgnToggleButtonHarness {
|
|
6
|
+
public readonly classes = themeClasses(toggleButtonControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly locator: Locator;
|
|
9
|
+
|
|
10
|
+
constructor(locator: Locator) {
|
|
11
|
+
this.locator = locator;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public async click(force = false) {
|
|
15
|
+
await this.locator.click({ force });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async expectActive(active: boolean) {
|
|
19
|
+
if (active) {
|
|
20
|
+
await expect(this.locator).toHaveClass(new RegExp(this.classes.active));
|
|
21
|
+
} else {
|
|
22
|
+
await expect(this.locator).not.toHaveClass(new RegExp(this.classes.active));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async expectDisabled(disabled: boolean) {
|
|
27
|
+
if (disabled) {
|
|
28
|
+
await expect(this.locator).toBeDisabled();
|
|
29
|
+
} else {
|
|
30
|
+
await expect(this.locator).not.toBeDisabled();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { tooltipControlTemplate } from '@ngneers/controls-themes/templates/tooltip';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import { expect, type Locator } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export class NgnTooltipHarness {
|
|
6
|
+
public readonly classes = themeClasses(tooltipControlTemplate);
|
|
7
|
+
|
|
8
|
+
private readonly _ngnDefer: Locator;
|
|
9
|
+
private readonly _contentWrapper: Locator;
|
|
10
|
+
|
|
11
|
+
public readonly locator: Locator;
|
|
12
|
+
public readonly content: Locator;
|
|
13
|
+
|
|
14
|
+
constructor(locator: Locator) {
|
|
15
|
+
this.locator = locator;
|
|
16
|
+
this._ngnDefer = locator.locator('ngn-defer');
|
|
17
|
+
this._contentWrapper = locator.locator(this.classes.content);
|
|
18
|
+
this.content = this._ngnDefer.locator('> *');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public async expectOpened(opened = true) {
|
|
22
|
+
await expect(this._contentWrapper).toBeVisible({ visible: opened });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async expectRendered(rendered = true): Promise<void> {
|
|
26
|
+
await expect(this._ngnDefer).toBeAttached();
|
|
27
|
+
await expect(async () => {
|
|
28
|
+
const el = await this._ngnDefer.elementHandle();
|
|
29
|
+
const hasContent =
|
|
30
|
+
!!(await el?.innerText()) || !!(await el?.evaluate(e => e.childElementCount));
|
|
31
|
+
if (rendered) {
|
|
32
|
+
expect(hasContent).toBeTruthy();
|
|
33
|
+
} else {
|
|
34
|
+
expect(hasContent).toBeFalsy();
|
|
35
|
+
}
|
|
36
|
+
}).toPass({ timeout: 2000 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { expect, type Locator } from '@playwright/test';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import { treeControlTemplate } from '@ngneers/controls-themes/templates/tree';
|
|
4
|
+
import { NgnScrollerHarness } from './scroller';
|
|
5
|
+
|
|
6
|
+
export class NgnTreeHarness {
|
|
7
|
+
public readonly classes = themeClasses(treeControlTemplate);
|
|
8
|
+
/** All currently rendered nodes (branches and leaves). */
|
|
9
|
+
public readonly node: Locator;
|
|
10
|
+
/** Leaf rows. */
|
|
11
|
+
public readonly item: Locator;
|
|
12
|
+
/** Branch rows (nodes with children). */
|
|
13
|
+
public readonly group: Locator;
|
|
14
|
+
public readonly toggle: Locator;
|
|
15
|
+
public readonly itemSelected: Locator;
|
|
16
|
+
public readonly itemHighlighted: Locator;
|
|
17
|
+
public readonly itemDisabled: Locator;
|
|
18
|
+
public readonly itemExpanded: Locator;
|
|
19
|
+
public readonly checkbox: Locator;
|
|
20
|
+
public readonly empty: Locator;
|
|
21
|
+
public readonly spinner: Locator;
|
|
22
|
+
public readonly scroller: NgnScrollerHarness;
|
|
23
|
+
|
|
24
|
+
constructor(public locator: Locator) {
|
|
25
|
+
this.node = locator.locator('[role="treeitem"]');
|
|
26
|
+
this.item = locator.locator(this.classes['item']);
|
|
27
|
+
this.group = locator.locator(this.classes['group']);
|
|
28
|
+
this.toggle = locator.locator(this.classes['toggle']);
|
|
29
|
+
this.itemSelected = locator.locator(this.classes['item-selected']);
|
|
30
|
+
this.itemHighlighted = locator.locator(this.classes['item-highlighted']);
|
|
31
|
+
this.itemDisabled = locator.locator(this.classes['item-disabled']);
|
|
32
|
+
this.itemExpanded = locator.locator(this.classes['item-expanded']);
|
|
33
|
+
this.checkbox = locator.locator(this.classes['item-checkbox']['root']);
|
|
34
|
+
this.empty = locator.locator(this.classes['empty']);
|
|
35
|
+
this.spinner = locator.locator('ngn-spinner');
|
|
36
|
+
this.scroller = new NgnScrollerHarness(locator.locator(this.classes['scroller']['root']));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A single node located by its exact (trimmed, case-insensitive) label. */
|
|
40
|
+
public getNode(text: string): Locator {
|
|
41
|
+
return this.node.filter({ hasText: new RegExp(`^\\s*${text}\\s*$`, 'i') });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** A single node located by the `testId` of its item. */
|
|
45
|
+
public getNodeByTestId(testId: string): Locator {
|
|
46
|
+
return this.node.filter({ has: this.locator.locator(`[data-testid="${testId}"]`) });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public expectNodeCount(count: number): Promise<void> {
|
|
50
|
+
return expect(this.node).toHaveCount(count);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public expectItemCount(count: number): Promise<void> {
|
|
54
|
+
return expect(this.item).toHaveCount(count);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public expectGroupCount(count: number): Promise<void> {
|
|
58
|
+
return expect(this.group).toHaveCount(count);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public expectNodeTexts(texts: string[]): Promise<void> {
|
|
62
|
+
return expect(this.node).toHaveText(texts.map(t => new RegExp(`^\\s*${t}\\s*$`)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Click a node's row (selects / checks it when selectable). */
|
|
66
|
+
public clickNode(text: string): Promise<void> {
|
|
67
|
+
return this.getNode(text).click();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Click a branch node's expand/collapse toggle. */
|
|
71
|
+
public toggleNode(text: string): Promise<void> {
|
|
72
|
+
return this.getNode(text).locator(this.classes['toggle']).click();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public expectExpanded(text: string, expanded = true): Promise<void> {
|
|
76
|
+
return expect(this.getNode(text)).toHaveAttribute('aria-expanded', String(expanded));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public expectSelected(text: string, selected = true): Promise<void> {
|
|
80
|
+
return expect(this.getNode(text)).toHaveAttribute('aria-selected', String(selected));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public expectChecked(text: string, state: 'true' | 'false' | 'mixed'): Promise<void> {
|
|
84
|
+
return expect(this.getNode(text)).toHaveAttribute('aria-checked', state);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public expectDisabled(text: string, disabled = true): Promise<void> {
|
|
88
|
+
return disabled
|
|
89
|
+
? expect(this.getNode(text)).toHaveAttribute('aria-disabled', 'true')
|
|
90
|
+
: expect(this.getNode(text)).not.toHaveAttribute('aria-disabled', 'true');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Assert the tree's `aria-activedescendant` points at the given node. */
|
|
94
|
+
public async expectActiveDescendant(text: string): Promise<void> {
|
|
95
|
+
const id = await this.getNode(text).getAttribute('id');
|
|
96
|
+
await expect(this.locator).toHaveAttribute('aria-activedescendant', id ?? '');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { uploadControlTemplate } from '@ngneers/controls-themes/templates/upload';
|
|
2
|
+
import { themeClasses } from '../utils/theme';
|
|
3
|
+
import { expect, type Locator } from '@playwright/test';
|
|
4
|
+
|
|
5
|
+
export class NgnUploadHarness {
|
|
6
|
+
public readonly classes = themeClasses(uploadControlTemplate);
|
|
7
|
+
|
|
8
|
+
public readonly locator: Locator;
|
|
9
|
+
public readonly zone: Locator;
|
|
10
|
+
public readonly input: Locator;
|
|
11
|
+
public readonly list: Locator;
|
|
12
|
+
public readonly items: Locator;
|
|
13
|
+
|
|
14
|
+
constructor(locator: Locator) {
|
|
15
|
+
this.locator = locator;
|
|
16
|
+
this.zone = locator.locator(this.classes.zone);
|
|
17
|
+
this.input = locator.locator('input[type=file]');
|
|
18
|
+
this.list = locator.locator(this.classes.list);
|
|
19
|
+
this.items = this.list.locator(this.classes.item);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Item `<li>` at the given index. */
|
|
23
|
+
public item(index: number = 0): Locator {
|
|
24
|
+
return this.items.nth(index);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public name(index: number = 0): Locator {
|
|
28
|
+
return this.item(index).locator(this.classes.name);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async expectItemCount(count: number): Promise<void> {
|
|
32
|
+
await expect(this.items).toHaveCount(count);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public async expectItemName(index: number, text: string): Promise<void> {
|
|
36
|
+
await expect(this.name(index)).toHaveText(text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public async expectItemState(
|
|
40
|
+
index: number,
|
|
41
|
+
state: 'pending' | 'uploading' | 'done' | 'failed'
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
const stateClass = this.classes[`item-${state}`].slice(1); // strip leading '.'
|
|
44
|
+
await expect(this.item(index)).toHaveClass(new RegExp(stateClass));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Select files through the projected native input (click-select path). */
|
|
48
|
+
public async selectFiles(
|
|
49
|
+
files: Array<{ name: string; mimeType: string; buffer: Buffer }>
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
await this.input.setInputFiles(files);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Action button ('upload' | 'cancel' | 'retry' | 'remove') on an item. */
|
|
55
|
+
public actionButton(index: number, label: string): Locator {
|
|
56
|
+
return this.item(index).getByLabel(new RegExp(`^${label}`, 'i'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The "Upload" button rendered in confirm mode (`confirmTrigger: 'all'`). */
|
|
60
|
+
public uploadAllButton(): Locator {
|
|
61
|
+
return this.locator.locator(this.classes.trigger);
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { avatarControlTemplate } from '@ngneers/controls-themes/templates/avatar';
|
|
2
|
+
import { themeClasses } from './theme';
|
|
3
|
+
import { buttonControlTemplate } from '@ngneers/controls-themes/templates/button';
|
|
4
|
+
import { calendarControlTemplate } from '@ngneers/controls-themes/templates/calendar';
|
|
5
|
+
import { checkboxControlTemplate } from '@ngneers/controls-themes/templates/checkbox';
|
|
6
|
+
import { inputControlTemplate } from '@ngneers/controls-themes/templates/input';
|
|
7
|
+
import { maskInputControlTemplate } from '@ngneers/controls-themes/templates/mask-input';
|
|
8
|
+
import { inputFieldControlTemplate } from '@ngneers/controls-themes/templates/input-field';
|
|
9
|
+
import { listBoxControlTemplate } from '@ngneers/controls-themes/templates/list-box';
|
|
10
|
+
import { popoverControlTemplate } from '@ngneers/controls-themes/templates/popover';
|
|
11
|
+
import { scrollerControlTemplate } from '@ngneers/controls-themes/templates/scroller';
|
|
12
|
+
import { selectControlTemplate } from '@ngneers/controls-themes/templates/select';
|
|
13
|
+
import { splitterControlTemplate } from '@ngneers/controls-themes/templates/splitter';
|
|
14
|
+
import { tabsControlTemplate } from '@ngneers/controls-themes/templates/tabs';
|
|
15
|
+
import { tooltipControlTemplate } from '@ngneers/controls-themes/templates/tooltip';
|
|
16
|
+
|
|
17
|
+
export const NGN_CLASSES = {
|
|
18
|
+
avatar: themeClasses(avatarControlTemplate),
|
|
19
|
+
button: themeClasses(buttonControlTemplate),
|
|
20
|
+
calendar: themeClasses(calendarControlTemplate),
|
|
21
|
+
checkbox: themeClasses(checkboxControlTemplate),
|
|
22
|
+
input: themeClasses(inputControlTemplate),
|
|
23
|
+
inputField: themeClasses(inputFieldControlTemplate),
|
|
24
|
+
maskInput: themeClasses(maskInputControlTemplate),
|
|
25
|
+
listBox: themeClasses(listBoxControlTemplate),
|
|
26
|
+
popover: themeClasses(popoverControlTemplate),
|
|
27
|
+
scroller: themeClasses(scrollerControlTemplate),
|
|
28
|
+
select: themeClasses(selectControlTemplate),
|
|
29
|
+
splitter: themeClasses(splitterControlTemplate),
|
|
30
|
+
tabs: themeClasses(tabsControlTemplate),
|
|
31
|
+
tooltip: themeClasses(tooltipControlTemplate),
|
|
32
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ControlTemplate, ThemeClasses } from '@ngneers/controls-themes';
|
|
2
|
+
|
|
3
|
+
export function themeClasses<CT extends ControlTemplate>(template: CT): ThemeClasses<CT> {
|
|
4
|
+
const result = {} as Record<string, unknown>;
|
|
5
|
+
for (const className of template.classNames) {
|
|
6
|
+
result[className] = `.ngn-${template.scope}-${className}`;
|
|
7
|
+
}
|
|
8
|
+
for (const dep of template.dependencies ?? []) {
|
|
9
|
+
const childClasses = themeClasses(dep.template);
|
|
10
|
+
// Non-projected deps get a `[ptDep]` marker class (`.ngn-{scope}-{depClass}`) applied to
|
|
11
|
+
// the child's host element (see `NgnPt`/`apply-theme.ts`'s `d()`). Whether a given child
|
|
12
|
+
// class then lives on that SAME host element or on an element nested inside the child's own
|
|
13
|
+
// template depends on which classNames the child passes as `hostClass` to
|
|
14
|
+
// `injectThemeTemplate` (e.g. list-box binds its own 'root' straight to its host, so marker
|
|
15
|
+
// and 'root' land on one element; input-field reserves a separate 'host' key for its host
|
|
16
|
+
// binding and keeps 'root' on an inner div, so marker and 'root' land on two elements).
|
|
17
|
+
// The theme package has no visibility into that per-component choice, so instead of guessing
|
|
18
|
+
// we match BOTH shapes via `:is()`: compound (same element) or descendant (nested element).
|
|
19
|
+
// Projected deps have no marker (no host the parent controls), so their classes stay raw,
|
|
20
|
+
// matching how `apply-theme.ts` resolves `projected: true` deps directly off the child's own
|
|
21
|
+
// scope.
|
|
22
|
+
result[dep.class] = dep.projected
|
|
23
|
+
? childClasses
|
|
24
|
+
: scopeByMarker(childClasses, `.ngn-${template.scope}-${dep.class}`);
|
|
25
|
+
}
|
|
26
|
+
return result as ThemeClasses<CT>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Scopes every selector string in a (possibly nested) theme-classes object to `marker`, matching
|
|
31
|
+
* either a compound (same-element) or descendant (nested-element) relationship — see the
|
|
32
|
+
* `dep.projected` branch above for why both shapes are needed. Recurses into nested dependency
|
|
33
|
+
* objects so grandchild slots are scoped transitively through every ancestor marker.
|
|
34
|
+
*/
|
|
35
|
+
function scopeByMarker<T>(classes: T, marker: string): T {
|
|
36
|
+
const scoped = {} as Record<string, unknown>;
|
|
37
|
+
for (const [key, value] of Object.entries(classes as Record<string, unknown>)) {
|
|
38
|
+
scoped[key] =
|
|
39
|
+
typeof value === 'string'
|
|
40
|
+
? `:is(${marker}${value}, ${marker} ${value})`
|
|
41
|
+
: scopeByMarker(value, marker);
|
|
42
|
+
}
|
|
43
|
+
return scoped as T;
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
|
2
|
+
{
|
|
3
|
+
"extends": "../../tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
// Resolve workspace peers to their built declarations (not source) so tsc treats
|
|
11
|
+
// them as external and emits only this package. Requires themes + controls built first.
|
|
12
|
+
"paths": {
|
|
13
|
+
"@ngneers/controls-themes": ["../themes/dist/index.d.ts"],
|
|
14
|
+
"@ngneers/controls-themes/*": ["../themes/dist/*"],
|
|
15
|
+
"@ngneers/controls/*": ["../controls/dist/*"]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"include": ["./src/**/*.ts"],
|
|
19
|
+
"exclude": ["**/*.spec.ts", "dist/**"]
|
|
20
|
+
}
|