@bitforgehq/ui 0.3.0
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/CHANGELOG.md +3665 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/ai/MANIFEST.index.json +383 -0
- package/ai/MANIFEST.json +7528 -0
- package/ai/REFERENCE.index.json +1461 -0
- package/ai/REFERENCE.json +7213 -0
- package/ai/UI_GUIDE.md +1159 -0
- package/ai/components/action-bar.md +209 -0
- package/ai/components/action-sheet.md +526 -0
- package/ai/components/alert-dialog.md +509 -0
- package/ai/components/alert.md +376 -0
- package/ai/components/avatar.md +146 -0
- package/ai/components/back-button.md +286 -0
- package/ai/components/badge.md +171 -0
- package/ai/components/button.md +431 -0
- package/ai/components/card.md +303 -0
- package/ai/components/checkbox.md +418 -0
- package/ai/components/chip-group.md +205 -0
- package/ai/components/chip.md +300 -0
- package/ai/components/content.md +474 -0
- package/ai/components/desktop-toast.md +420 -0
- package/ai/components/dialog.md +962 -0
- package/ai/components/empty-state.md +146 -0
- package/ai/components/field.md +319 -0
- package/ai/components/header.md +809 -0
- package/ai/components/icon-button.md +221 -0
- package/ai/components/icon.md +176 -0
- package/ai/components/input.md +455 -0
- package/ai/components/list-item.md +326 -0
- package/ai/components/list.md +178 -0
- package/ai/components/menu-button.md +377 -0
- package/ai/components/menu.md +661 -0
- package/ai/components/modal.md +613 -0
- package/ai/components/number-input.md +423 -0
- package/ai/components/page.md +299 -0
- package/ai/components/progress.md +215 -0
- package/ai/components/radio-group.md +432 -0
- package/ai/components/refresher.md +377 -0
- package/ai/components/segmented.md +517 -0
- package/ai/components/select.md +510 -0
- package/ai/components/sheet.md +845 -0
- package/ai/components/spinner.md +171 -0
- package/ai/components/stack-outlet.md +661 -0
- package/ai/components/tab-bar.md +427 -0
- package/ai/components/tab.md +295 -0
- package/ai/components/tabs.md +408 -0
- package/ai/components/textarea.md +371 -0
- package/ai/components/toast.md +522 -0
- package/ai/components/toggle.md +387 -0
- package/ai/reference/bf-action-directive.md +21 -0
- package/ai/reference/bf-action-sheet-action-role.md +27 -0
- package/ai/reference/bf-action-sheet-action.md +28 -0
- package/ai/reference/bf-action-sheet-options.md +28 -0
- package/ai/reference/bf-action-sheet-service.md +33 -0
- package/ai/reference/bf-alert-dialog-button-role.md +27 -0
- package/ai/reference/bf-alert-dialog-button.md +27 -0
- package/ai/reference/bf-alert-dialog-options.md +29 -0
- package/ai/reference/bf-alert-dialog-service.md +34 -0
- package/ai/reference/bf-alert-variant.md +28 -0
- package/ai/reference/bf-autocapitalize.md +30 -0
- package/ai/reference/bf-avatar-size.md +27 -0
- package/ai/reference/bf-avatar-variant.md +28 -0
- package/ai/reference/bf-back-at-root.md +28 -0
- package/ai/reference/bf-back-button-service.md +40 -0
- package/ai/reference/bf-badge-variant.md +29 -0
- package/ai/reference/bf-button-size.md +29 -0
- package/ai/reference/bf-button-type.md +28 -0
- package/ai/reference/bf-button-variant.md +29 -0
- package/ai/reference/bf-card-padding.md +26 -0
- package/ai/reference/bf-card-variant.md +28 -0
- package/ai/reference/bf-chip-group-mode.md +27 -0
- package/ai/reference/bf-chip-variant.md +26 -0
- package/ai/reference/bf-compare-with.md +24 -0
- package/ai/reference/bf-component-overlay-ref.md +33 -0
- package/ai/reference/bf-confirm-button-role.md +26 -0
- package/ai/reference/bf-confirm-button.md +27 -0
- package/ai/reference/bf-confirm-options.md +31 -0
- package/ai/reference/bf-confirm-size.md +26 -0
- package/ai/reference/bf-content-scroll-event.md +21 -0
- package/ai/reference/bf-defaults-options.md +32 -0
- package/ai/reference/bf-defaults.md +28 -0
- package/ai/reference/bf-desktop-toast-config.md +28 -0
- package/ai/reference/bf-desktop-toast-harness.md +33 -0
- package/ai/reference/bf-desktop-toast-options.md +28 -0
- package/ai/reference/bf-desktop-toast-position.md +28 -0
- package/ai/reference/bf-desktop-toast-service.md +42 -0
- package/ai/reference/bf-dialog-config.md +44 -0
- package/ai/reference/bf-dialog-harness.md +52 -0
- package/ai/reference/bf-dialog-ref.md +44 -0
- package/ai/reference/bf-dialog-role.md +26 -0
- package/ai/reference/bf-dialog-service.md +55 -0
- package/ai/reference/bf-dialog-size.md +31 -0
- package/ai/reference/bf-dialog-template-context.md +32 -0
- package/ai/reference/bf-empty-state-heading-level.md +26 -0
- package/ai/reference/bf-enter-key-hint.md +31 -0
- package/ai/reference/bf-field-control-class.md +32 -0
- package/ai/reference/bf-field-control-state.md +28 -0
- package/ai/reference/bf-field-size.md +29 -0
- package/ai/reference/bf-haptics-impact-style.md +27 -0
- package/ai/reference/bf-haptics-notification-type.md +27 -0
- package/ai/reference/bf-haptics-service.md +41 -0
- package/ai/reference/bf-harness-filters.md +29 -0
- package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
- package/ai/reference/bf-icon-button-variant.md +28 -0
- package/ai/reference/bf-icon-size.md +27 -0
- package/ai/reference/bf-input-autocapitalize.md +36 -0
- package/ai/reference/bf-input-enter-key-hint.md +38 -0
- package/ai/reference/bf-input-input-mode.md +38 -0
- package/ai/reference/bf-input-mode.md +32 -0
- package/ai/reference/bf-input-type.md +35 -0
- package/ai/reference/bf-jsdom-shim-options.md +22 -0
- package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
- package/ai/reference/bf-keyboard-assist-directive.md +185 -0
- package/ai/reference/bf-keyboard-phase.md +30 -0
- package/ai/reference/bf-keyboard-service.md +50 -0
- package/ai/reference/bf-labels-overrides.md +26 -0
- package/ai/reference/bf-labels.md +44 -0
- package/ai/reference/bf-menu-button-harness.md +40 -0
- package/ai/reference/bf-menu-button-variant.md +27 -0
- package/ai/reference/bf-menu-divider-directive.md +13 -0
- package/ai/reference/bf-menu-group-directive.md +14 -0
- package/ai/reference/bf-menu-harness.md +55 -0
- package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
- package/ai/reference/bf-menu-item-directive.md +31 -0
- package/ai/reference/bf-menu-item-harness.md +52 -0
- package/ai/reference/bf-menu-item-radio-directive.md +29 -0
- package/ai/reference/bf-menu-placement.md +29 -0
- package/ai/reference/bf-menu-trigger-directive.md +46 -0
- package/ai/reference/bf-modal-options.md +30 -0
- package/ai/reference/bf-modal-presentation.md +26 -0
- package/ai/reference/bf-modal-service.md +38 -0
- package/ai/reference/bf-nav-controller.md +59 -0
- package/ai/reference/bf-nav-direction.md +23 -0
- package/ai/reference/bf-nav-target.md +21 -0
- package/ai/reference/bf-number-input-mode.md +28 -0
- package/ai/reference/bf-number-input-step.md +25 -0
- package/ai/reference/bf-overlay-auto-focus.md +29 -0
- package/ai/reference/bf-overlay-controller.md +22 -0
- package/ai/reference/bf-overlay-host.md +36 -0
- package/ai/reference/bf-overlay-options.md +53 -0
- package/ai/reference/bf-overlay-position.md +30 -0
- package/ai/reference/bf-overlay-ref.md +53 -0
- package/ai/reference/bf-overlay-role.md +27 -0
- package/ai/reference/bf-overlay-service.md +53 -0
- package/ai/reference/bf-overlay-stack-options.md +24 -0
- package/ai/reference/bf-overlay-stack.md +81 -0
- package/ai/reference/bf-overlay-template-context.md +24 -0
- package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
- package/ai/reference/bf-page-context.md +24 -0
- package/ai/reference/bf-platform-service.md +53 -0
- package/ai/reference/bf-platform.md +28 -0
- package/ai/reference/bf-progress-size.md +26 -0
- package/ai/reference/bf-progress-tone.md +28 -0
- package/ai/reference/bf-radio-group-harness.md +49 -0
- package/ai/reference/bf-radio-group-orientation.md +26 -0
- package/ai/reference/bf-radio-option.md +30 -0
- package/ai/reference/bf-refresher-refresh-event.md +22 -0
- package/ai/reference/bf-segment-option.md +29 -0
- package/ai/reference/bf-segmented-harness.md +48 -0
- package/ai/reference/bf-segmented-size.md +29 -0
- package/ai/reference/bf-select-harness.md +70 -0
- package/ai/reference/bf-select-option.md +29 -0
- package/ai/reference/bf-sheet-auto-focus.md +29 -0
- package/ai/reference/bf-sheet-options.md +32 -0
- package/ai/reference/bf-sheet-ref.md +45 -0
- package/ai/reference/bf-sheet-service.md +34 -0
- package/ai/reference/bf-shell-config-mode.md +28 -0
- package/ai/reference/bf-shell-config.md +38 -0
- package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
- package/ai/reference/bf-shell-dynamic-type.md +25 -0
- package/ai/reference/bf-shell-mode.md +28 -0
- package/ai/reference/bf-shell-testing-options.md +26 -0
- package/ai/reference/bf-size.md +45 -0
- package/ai/reference/bf-spinner-size.md +27 -0
- package/ai/reference/bf-stack-change.md +22 -0
- package/ai/reference/bf-stack-reuse-strategy.md +32 -0
- package/ai/reference/bf-stack-transition-event.md +26 -0
- package/ai/reference/bf-stack-transition.md +26 -0
- package/ai/reference/bf-stacked-overlay.md +23 -0
- package/ai/reference/bf-system-bars-style.md +38 -0
- package/ai/reference/bf-tab-bar-controller.md +51 -0
- package/ai/reference/bf-tab-bar-host.md +52 -0
- package/ai/reference/bf-tab-context.md +32 -0
- package/ai/reference/bf-tab-links.md +37 -0
- package/ai/reference/bf-testing-options.md +27 -0
- package/ai/reference/bf-text-scale-service.md +35 -0
- package/ai/reference/bf-theme.md +27 -0
- package/ai/reference/bf-toast-action.md +25 -0
- package/ai/reference/bf-toast-controller.md +17 -0
- package/ai/reference/bf-toast-dismiss-reason.md +29 -0
- package/ai/reference/bf-toast-options.md +29 -0
- package/ai/reference/bf-toast-position.md +26 -0
- package/ai/reference/bf-toast-ref.md +41 -0
- package/ai/reference/bf-toast-service.md +39 -0
- package/ai/reference/bf-toggle-label-position.md +26 -0
- package/ai/reference/bf-transition-kind.md +28 -0
- package/ai/reference/bf-validation-message.md +31 -0
- package/ai/reference/bf-validation-messages.md +33 -0
- package/ai/reference/bf-view-event.md +28 -0
- package/ai/reference/bf-view-lifecycle.md +41 -0
- package/ai/reference/bf-view-state.md +28 -0
- package/ai/reference/bf_default_labels.md +21 -0
- package/ai/reference/bf_default_values.md +21 -0
- package/ai/reference/bf_defaults.md +21 -0
- package/ai/reference/bf_desktop_toast_defaults.md +21 -0
- package/ai/reference/bf_dialog_data.md +17 -0
- package/ai/reference/bf_labels.md +21 -0
- package/ai/reference/bf_overlay_data.md +19 -0
- package/ai/reference/bf_overlay_host.md +21 -0
- package/ai/reference/bf_sheet_default_snap_points.md +17 -0
- package/ai/reference/bf_shell_config.md +21 -0
- package/ai/reference/bf_shell_defaults.md +21 -0
- package/ai/reference/bf_tab_bar.md +21 -0
- package/ai/reference/bf_tab_bar_controller.md +21 -0
- package/ai/reference/bf_tab_context.md +21 -0
- package/ai/reference/bf_tab_links.md +21 -0
- package/ai/reference/bf_theme_storage.md +19 -0
- package/ai/reference/create-transition.md +36 -0
- package/ai/reference/duration_fast.md +17 -0
- package/ai/reference/duration_md.md +17 -0
- package/ai/reference/duration_overlay.md +17 -0
- package/ai/reference/duration_push.md +17 -0
- package/ai/reference/fade-down.md +26 -0
- package/ai/reference/fade-in.md +25 -0
- package/ai/reference/fade-out.md +25 -0
- package/ai/reference/fade-up.md +26 -0
- package/ai/reference/inject-shell-mode.md +23 -0
- package/ai/reference/install-bf-jsdom-shims.md +31 -0
- package/ai/reference/ios_duration.md +17 -0
- package/ai/reference/ios_easing.md +17 -0
- package/ai/reference/is-text-entry.md +25 -0
- package/ai/reference/max_complete_ms.md +17 -0
- package/ai/reference/md-transition.md +31 -0
- package/ai/reference/md_duration.md +17 -0
- package/ai/reference/md_easing.md +17 -0
- package/ai/reference/overlay_duration.md +21 -0
- package/ai/reference/overlay_easing.md +19 -0
- package/ai/reference/overlay_reduced_duration.md +17 -0
- package/ai/reference/pop-frames-at.md +25 -0
- package/ai/reference/prefers-reduced-motion.md +25 -0
- package/ai/reference/provide-bf-defaults.md +33 -0
- package/ai/reference/provide-bf-desktop-toast.md +28 -0
- package/ai/reference/provide-bf-labels.md +38 -0
- package/ai/reference/provide-bf-shell-testing.md +34 -0
- package/ai/reference/provide-bf-shell.md +41 -0
- package/ai/reference/provide-bf-testing.md +33 -0
- package/ai/reference/reduced-transition.md +28 -0
- package/ai/reference/reduced_duration.md +17 -0
- package/ai/reference/resolve-shell-mode.md +33 -0
- package/ai/reference/scale-in.md +25 -0
- package/ai/reference/settle-duration.md +25 -0
- package/ai/reference/settle-pop.md +33 -0
- package/ai/reference/settle_easing.md +17 -0
- package/ai/reference/should-complete-pop.md +26 -0
- package/ai/reference/slide-down.md +26 -0
- package/ai/reference/slide-up.md +26 -0
- package/ai/reference/with-bf-shell-routing.md +19 -0
- package/bin/bf-ui.js +81 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui.mjs +7765 -0
- package/fesm2022/bitforgehq-ui.mjs.map +1 -0
- package/llms-full.txt +25394 -0
- package/llms.txt +329 -0
- package/package.json +94 -0
- package/theme.css +253 -0
- package/types/bitforgehq-ui-icons.d.ts +50 -0
- package/types/bitforgehq-ui-mobile.d.ts +2914 -0
- package/types/bitforgehq-ui-number-input.d.ts +282 -0
- package/types/bitforgehq-ui-overlay.d.ts +1514 -0
- package/types/bitforgehq-ui-testing.d.ts +495 -0
- package/types/bitforgehq-ui.d.ts +3499 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { makeEnvironmentProviders } from "@angular/core";
|
|
2
|
+
import { provideBfDefaults, provideBfLabels, ɵprovideBfDocumentReset } from "@bitforgehq/ui";
|
|
3
|
+
import { ComponentHarness, HarnessPredicate, TestKey } from "@angular/cdk/testing";
|
|
4
|
+
const REDUCED_MOTION_QUERY = /\(\s*prefers-reduced-motion\s*:\s*reduce\s*\)/;
|
|
5
|
+
var BfShimAnimation = class extends EventTarget {
|
|
6
|
+
currentTime = 0;
|
|
7
|
+
effect = null;
|
|
8
|
+
id = "";
|
|
9
|
+
oncancel = null;
|
|
10
|
+
onfinish = null;
|
|
11
|
+
onremove = null;
|
|
12
|
+
overallProgress = 1;
|
|
13
|
+
pending = false;
|
|
14
|
+
playState = "running";
|
|
15
|
+
playbackRate = 1;
|
|
16
|
+
finished = Promise.resolve(this);
|
|
17
|
+
ready = Promise.resolve(this);
|
|
18
|
+
replaceState = "active";
|
|
19
|
+
startTime = null;
|
|
20
|
+
timeline = null;
|
|
21
|
+
cancel() {
|
|
22
|
+
this.playState = "idle";
|
|
23
|
+
}
|
|
24
|
+
commitStyles() {}
|
|
25
|
+
finish() {
|
|
26
|
+
this.playState = "finished";
|
|
27
|
+
}
|
|
28
|
+
pause() {
|
|
29
|
+
this.playState = "paused";
|
|
30
|
+
}
|
|
31
|
+
persist() {}
|
|
32
|
+
play() {
|
|
33
|
+
this.playState = "running";
|
|
34
|
+
}
|
|
35
|
+
reverse() {}
|
|
36
|
+
updatePlaybackRate(rate) {
|
|
37
|
+
this.playbackRate = rate;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var BfPointerEventShim = class extends MouseEvent {
|
|
41
|
+
pointerId;
|
|
42
|
+
isPrimary;
|
|
43
|
+
pointerType;
|
|
44
|
+
width;
|
|
45
|
+
height;
|
|
46
|
+
pressure;
|
|
47
|
+
constructor(type, init = {}) {
|
|
48
|
+
super(type, init);
|
|
49
|
+
this.pointerId = init.pointerId ?? 0;
|
|
50
|
+
this.isPrimary = init.isPrimary ?? false;
|
|
51
|
+
this.pointerType = init.pointerType ?? "";
|
|
52
|
+
this.width = init.width ?? 1;
|
|
53
|
+
this.height = init.height ?? 1;
|
|
54
|
+
this.pressure = init.pressure ?? 0;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function staticMediaQueryList(media, matches) {
|
|
58
|
+
return {
|
|
59
|
+
matches,
|
|
60
|
+
media,
|
|
61
|
+
onchange: null,
|
|
62
|
+
addListener: () => void 0,
|
|
63
|
+
removeListener: () => void 0,
|
|
64
|
+
addEventListener: () => void 0,
|
|
65
|
+
removeEventListener: () => void 0,
|
|
66
|
+
dispatchEvent: () => false
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function installGlobal(target, key, value) {
|
|
70
|
+
const previous = Object.getOwnPropertyDescriptor(target, key);
|
|
71
|
+
if (previous && !previous.configurable) {
|
|
72
|
+
const before = previous.get ? previous.get.call(target) : previous.value;
|
|
73
|
+
if (!Reflect.set(target, key, value)) return null;
|
|
74
|
+
return () => {
|
|
75
|
+
Reflect.set(target, key, before);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
Object.defineProperty(target, key, {
|
|
79
|
+
value,
|
|
80
|
+
writable: true,
|
|
81
|
+
configurable: true
|
|
82
|
+
});
|
|
83
|
+
return () => {
|
|
84
|
+
if (previous) Object.defineProperty(target, key, previous);
|
|
85
|
+
else Reflect.deleteProperty(target, key);
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function installBfJsdomShims(options = {}) {
|
|
89
|
+
const removers = [];
|
|
90
|
+
const add = (remove) => {
|
|
91
|
+
if (remove) removers.push(remove);
|
|
92
|
+
};
|
|
93
|
+
if (typeof Element !== "undefined" && typeof Element.prototype.animate !== "function") add(installGlobal(Element.prototype, "animate", function animate(_keyframes, _timing) {
|
|
94
|
+
return new BfShimAnimation();
|
|
95
|
+
}));
|
|
96
|
+
if (typeof globalThis.PointerEvent !== "function" && typeof MouseEvent === "function") add(installGlobal(globalThis, "PointerEvent", BfPointerEventShim));
|
|
97
|
+
if ((options.matchMedia ?? true) && typeof window !== "undefined" && typeof window.matchMedia !== "function") {
|
|
98
|
+
const reducedMotion = options.reducedMotion ?? false;
|
|
99
|
+
const media = (query) => staticMediaQueryList(query, reducedMotion && REDUCED_MOTION_QUERY.test(query));
|
|
100
|
+
add(installGlobal(window, "matchMedia", media));
|
|
101
|
+
}
|
|
102
|
+
return () => {
|
|
103
|
+
for (const remove of removers.splice(0).reverse()) remove();
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function provideBfTesting(options = {}) {
|
|
107
|
+
const providers = [];
|
|
108
|
+
if (options.defaults) providers.push(provideBfDefaults(options.defaults));
|
|
109
|
+
if (options.labels) providers.push(provideBfLabels(options.labels));
|
|
110
|
+
return makeEnvironmentProviders([...providers, ɵprovideBfDocumentReset()]);
|
|
111
|
+
}
|
|
112
|
+
var BfSelectHarness = class BfSelectHarness extends ComponentHarness {
|
|
113
|
+
static hostSelector = "bf-select";
|
|
114
|
+
select = this.locatorFor("select");
|
|
115
|
+
label = this.locatorForOptional("label");
|
|
116
|
+
error = this.locatorForOptional("p[id$=\"-error\"]");
|
|
117
|
+
help = this.locatorForOptional("p[id$=\"-help\"]");
|
|
118
|
+
nativeOptions = this.locatorForAll("option");
|
|
119
|
+
static with(filters = {}) {
|
|
120
|
+
return new HarnessPredicate(BfSelectHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
|
|
121
|
+
}
|
|
122
|
+
async getLabel() {
|
|
123
|
+
const label = await this.label();
|
|
124
|
+
if (label) return (await label.text({ exclude: "[aria-hidden=\"true\"]" })).trim();
|
|
125
|
+
return await (await this.select()).getAttribute("aria-label") ?? "";
|
|
126
|
+
}
|
|
127
|
+
async getValueLabel() {
|
|
128
|
+
for (const option of await this.options()) if (option.choice && await option.element.getProperty("selected")) return option.label;
|
|
129
|
+
return "";
|
|
130
|
+
}
|
|
131
|
+
async getOptionLabels() {
|
|
132
|
+
return (await this.options()).filter((option) => option.choice).map((option) => option.label);
|
|
133
|
+
}
|
|
134
|
+
async selectOption(label) {
|
|
135
|
+
if (await this.isDisabled()) throw new Error(`BfSelectHarness: the select is disabled, so "${label}" cannot be chosen`);
|
|
136
|
+
const options = await this.options();
|
|
137
|
+
const option = options.find((o) => o.choice && o.label === label);
|
|
138
|
+
if (!option) {
|
|
139
|
+
const known = options.filter((o) => o.choice).map((o) => o.label);
|
|
140
|
+
throw new Error(`BfSelectHarness: no option "${label}" (options: ${known.join(", ")})`);
|
|
141
|
+
}
|
|
142
|
+
if (await option.element.getProperty("disabled")) throw new Error(`BfSelectHarness: the option "${label}" is disabled`);
|
|
143
|
+
const select = await this.select();
|
|
144
|
+
await select.setInputValue(await option.element.getProperty("value"));
|
|
145
|
+
await select.dispatchEvent("change");
|
|
146
|
+
}
|
|
147
|
+
async isDisabled() {
|
|
148
|
+
return (await this.select()).getProperty("disabled");
|
|
149
|
+
}
|
|
150
|
+
async isReadonly() {
|
|
151
|
+
return await (await this.select()).getAttribute("aria-readonly") === "true";
|
|
152
|
+
}
|
|
153
|
+
async isRequired() {
|
|
154
|
+
return (await this.select()).getProperty("required");
|
|
155
|
+
}
|
|
156
|
+
async getErrorText() {
|
|
157
|
+
return (await (await this.error())?.text())?.trim() ?? "";
|
|
158
|
+
}
|
|
159
|
+
async getHelpText() {
|
|
160
|
+
return (await (await this.help())?.text())?.trim() ?? "";
|
|
161
|
+
}
|
|
162
|
+
async focus() {
|
|
163
|
+
await (await this.select()).focus();
|
|
164
|
+
}
|
|
165
|
+
async blur() {
|
|
166
|
+
await (await this.select()).blur();
|
|
167
|
+
}
|
|
168
|
+
async options() {
|
|
169
|
+
return Promise.all((await this.nativeOptions()).map(async (element, index) => {
|
|
170
|
+
const [label, value, hidden, disabled] = await Promise.all([
|
|
171
|
+
element.text(),
|
|
172
|
+
element.getAttribute("value"),
|
|
173
|
+
element.getProperty("hidden"),
|
|
174
|
+
element.getProperty("disabled")
|
|
175
|
+
]);
|
|
176
|
+
const placeholder = index === 0 && (hidden || value === "" && disabled);
|
|
177
|
+
return {
|
|
178
|
+
element,
|
|
179
|
+
label: label.trim(),
|
|
180
|
+
choice: !placeholder
|
|
181
|
+
};
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
var BfRadioGroupHarness = class BfRadioGroupHarness extends ComponentHarness {
|
|
186
|
+
static hostSelector = "bf-radio-group";
|
|
187
|
+
fieldset = this.locatorFor("fieldset");
|
|
188
|
+
legend = this.locatorForOptional("legend");
|
|
189
|
+
error = this.locatorForOptional("p[id$=\"-error\"]");
|
|
190
|
+
radios = this.locatorForAll("[data-options] > label input[type=\"radio\"]");
|
|
191
|
+
labels = this.locatorForAll("[data-options] > label > span:last-child > span:first-child");
|
|
192
|
+
static with(filters = {}) {
|
|
193
|
+
return new HarnessPredicate(BfRadioGroupHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
|
|
194
|
+
}
|
|
195
|
+
async getLabel() {
|
|
196
|
+
const legend = await this.legend();
|
|
197
|
+
if (legend) return (await legend.text()).trim();
|
|
198
|
+
return await (await this.fieldset()).getAttribute("aria-label") ?? "";
|
|
199
|
+
}
|
|
200
|
+
async getOptionLabels() {
|
|
201
|
+
return Promise.all((await this.labels()).map(async (label) => (await label.text()).trim()));
|
|
202
|
+
}
|
|
203
|
+
async getSelectedLabel() {
|
|
204
|
+
const [radios, labels] = await Promise.all([this.radios(), this.getOptionLabels()]);
|
|
205
|
+
for (const [index, radio] of radios.entries()) if (await radio.getProperty("checked")) return labels[index] ?? null;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
async select(label) {
|
|
209
|
+
const [radios, labels] = await Promise.all([this.radios(), this.getOptionLabels()]);
|
|
210
|
+
const radio = radios[labels.indexOf(label)];
|
|
211
|
+
if (!radio) throw new Error(`BfRadioGroupHarness: no option "${label}" (options: ${labels.join(", ")})`);
|
|
212
|
+
if (await radio.matchesSelector(":disabled")) throw new Error(`BfRadioGroupHarness: the option "${label}" is disabled`);
|
|
213
|
+
await radio.click();
|
|
214
|
+
}
|
|
215
|
+
async isDisabled() {
|
|
216
|
+
return (await this.fieldset()).getProperty("disabled");
|
|
217
|
+
}
|
|
218
|
+
async getErrorText() {
|
|
219
|
+
return (await (await this.error())?.text())?.trim() ?? "";
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
var BfSegmentedHarness = class BfSegmentedHarness extends ComponentHarness {
|
|
223
|
+
static hostSelector = "bf-segmented";
|
|
224
|
+
group = this.locatorFor("[role=\"radiogroup\"]");
|
|
225
|
+
error = this.locatorForOptional("p[id$=\"-error\"]");
|
|
226
|
+
segments = this.locatorForAll("[role=\"radio\"]");
|
|
227
|
+
static with(filters = {}) {
|
|
228
|
+
return new HarnessPredicate(BfSegmentedHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
|
|
229
|
+
}
|
|
230
|
+
async getLabel() {
|
|
231
|
+
return await (await this.group()).getAttribute("aria-label") ?? "";
|
|
232
|
+
}
|
|
233
|
+
async getOptionLabels() {
|
|
234
|
+
return Promise.all((await this.segments()).map(async (segment) => (await segment.text()).trim()));
|
|
235
|
+
}
|
|
236
|
+
async getSelectedLabel() {
|
|
237
|
+
for (const segment of await this.segments()) if (await segment.getAttribute("aria-checked") === "true") return (await segment.text()).trim();
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
async select(label) {
|
|
241
|
+
const [segments, labels] = await Promise.all([this.segments(), this.getOptionLabels()]);
|
|
242
|
+
const segment = segments[labels.indexOf(label)];
|
|
243
|
+
if (!segment) throw new Error(`BfSegmentedHarness: no segment "${label}" (segments: ${labels.join(", ")})`);
|
|
244
|
+
if (await segment.matchesSelector(":disabled")) throw new Error(`BfSegmentedHarness: the segment "${label}" is disabled`);
|
|
245
|
+
await segment.click();
|
|
246
|
+
}
|
|
247
|
+
async isDisabled() {
|
|
248
|
+
const segments = await this.segments();
|
|
249
|
+
const disabled = await Promise.all(segments.map((segment) => segment.getProperty("disabled")));
|
|
250
|
+
return segments.length > 0 && disabled.every(Boolean);
|
|
251
|
+
}
|
|
252
|
+
async getErrorText() {
|
|
253
|
+
return (await (await this.error())?.text())?.trim() ?? "";
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
var BfDialogHarness = class BfDialogHarness extends ComponentHarness {
|
|
257
|
+
static hostSelector = "bf-dialog";
|
|
258
|
+
title = this.locatorForOptional("[data-dialog-header] h2");
|
|
259
|
+
body = this.locatorFor("[data-dialog-body]");
|
|
260
|
+
closeButton = this.locatorForOptional("[data-dialog-close] button");
|
|
261
|
+
buttons = this.locatorForAll("button");
|
|
262
|
+
static with(filters = {}) {
|
|
263
|
+
return new HarnessPredicate(BfDialogHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getTitle(), label));
|
|
264
|
+
}
|
|
265
|
+
async getTitle() {
|
|
266
|
+
return (await (await this.title())?.text())?.trim() ?? "";
|
|
267
|
+
}
|
|
268
|
+
async getText() {
|
|
269
|
+
return (await (await this.body()).text()).trim();
|
|
270
|
+
}
|
|
271
|
+
async getRole() {
|
|
272
|
+
const id = await (await this.host()).getAttribute("data-bf-dialog-id");
|
|
273
|
+
if (!id) return null;
|
|
274
|
+
const root = this.documentRootLocatorFactory();
|
|
275
|
+
for (const panel of await root.locatorForAll(".bf-overlay-panel[id]")()) {
|
|
276
|
+
const paneId = await panel.getAttribute("id");
|
|
277
|
+
if (await root.locatorForOptional(`[id="${paneId}"] bf-dialog[data-bf-dialog-id="${id}"]`)()) return panel.getAttribute("role");
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
async clickButton(label) {
|
|
282
|
+
const buttons = await this.buttons();
|
|
283
|
+
const texts = await Promise.all(buttons.map(async (button) => (await button.text()).trim()));
|
|
284
|
+
const button = buttons[texts.indexOf(label)];
|
|
285
|
+
if (!button) {
|
|
286
|
+
const known = texts.filter(Boolean);
|
|
287
|
+
throw new Error(`BfDialogHarness: no button "${label}" (buttons: ${known.join(", ")})`);
|
|
288
|
+
}
|
|
289
|
+
await button.click();
|
|
290
|
+
}
|
|
291
|
+
async close() {
|
|
292
|
+
const button = await this.closeButton();
|
|
293
|
+
if (!button) throw new Error("BfDialogHarness: the dialog has no close button");
|
|
294
|
+
await button.click();
|
|
295
|
+
}
|
|
296
|
+
async pressEscape() {
|
|
297
|
+
await (await this.body()).sendKeys(TestKey.ESCAPE);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
var BfMenuItemHarness = class BfMenuItemHarness extends ComponentHarness {
|
|
301
|
+
static hostSelector = "button[bfMenuItem], a[bfMenuItem], button[bfMenuItemCheckbox], button[bfMenuItemRadio]";
|
|
302
|
+
static with(filters = {}) {
|
|
303
|
+
return new HarnessPredicate(BfMenuItemHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getText(), label));
|
|
304
|
+
}
|
|
305
|
+
async getText() {
|
|
306
|
+
return (await (await this.host()).text()).trim();
|
|
307
|
+
}
|
|
308
|
+
async isDisabled() {
|
|
309
|
+
return await (await this.host()).getAttribute("aria-disabled") === "true";
|
|
310
|
+
}
|
|
311
|
+
async isChecked() {
|
|
312
|
+
return await (await this.host()).getAttribute("aria-checked") === "true";
|
|
313
|
+
}
|
|
314
|
+
async hasSubmenu() {
|
|
315
|
+
return await (await this.host()).getAttribute("aria-haspopup") === "menu";
|
|
316
|
+
}
|
|
317
|
+
async click() {
|
|
318
|
+
await (await this.host()).click();
|
|
319
|
+
}
|
|
320
|
+
async getSubmenu() {
|
|
321
|
+
const id = await (await this.host()).getAttribute("aria-controls");
|
|
322
|
+
return id ? menuById(this.documentRootLocatorFactory(), id) : null;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
var BfMenuHarness = class BfMenuHarness extends ComponentHarness {
|
|
326
|
+
static hostSelector = "bf-menu";
|
|
327
|
+
static with(filters = {}) {
|
|
328
|
+
return new HarnessPredicate(BfMenuHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
|
|
329
|
+
}
|
|
330
|
+
async getLabel() {
|
|
331
|
+
const host = await this.host();
|
|
332
|
+
const label = await host.getAttribute("aria-label");
|
|
333
|
+
if (label) return label;
|
|
334
|
+
const by = await host.getAttribute("aria-labelledby");
|
|
335
|
+
return (await (by ? await this.documentRootLocatorFactory().locatorForOptional(`[id="${by}"]`)() : null)?.text())?.trim() ?? "";
|
|
336
|
+
}
|
|
337
|
+
async getItems(filter = {}) {
|
|
338
|
+
return this.locatorForAll(BfMenuItemHarness.with(filter))();
|
|
339
|
+
}
|
|
340
|
+
async clickItem(label, ...submenuPath) {
|
|
341
|
+
const [item] = await this.getItems({ label });
|
|
342
|
+
if (!item) {
|
|
343
|
+
const known = await Promise.all((await this.getItems()).map((i) => i.getText()));
|
|
344
|
+
throw new Error(`BfMenuHarness: no item "${label}" (items: ${known.join(", ")})`);
|
|
345
|
+
}
|
|
346
|
+
await item.click();
|
|
347
|
+
const [next, ...rest] = submenuPath;
|
|
348
|
+
if (next === void 0) return;
|
|
349
|
+
const submenu = await item.getSubmenu();
|
|
350
|
+
if (!submenu) throw new Error(`BfMenuHarness: the item "${label}" opened no submenu`);
|
|
351
|
+
await submenu.clickItem(next, ...rest);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
async function menuById(root, id) {
|
|
355
|
+
return root.locatorForOptional(BfMenuHarness.with({ selector: `[id="${id}"]` }))();
|
|
356
|
+
}
|
|
357
|
+
var BfMenuButtonHarness = class BfMenuButtonHarness extends ComponentHarness {
|
|
358
|
+
static hostSelector = "bf-menu-button";
|
|
359
|
+
button = this.locatorFor("button");
|
|
360
|
+
static with(filters = {}) {
|
|
361
|
+
return new HarnessPredicate(BfMenuButtonHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label));
|
|
362
|
+
}
|
|
363
|
+
async getLabel() {
|
|
364
|
+
const button = await this.button();
|
|
365
|
+
return await button.getAttribute("aria-label") ?? (await button.text()).trim();
|
|
366
|
+
}
|
|
367
|
+
async open() {
|
|
368
|
+
if (!await this.isOpen()) await (await this.button()).click();
|
|
369
|
+
}
|
|
370
|
+
async isOpen() {
|
|
371
|
+
return await (await this.button()).getAttribute("aria-expanded") === "true";
|
|
372
|
+
}
|
|
373
|
+
async getMenu() {
|
|
374
|
+
await this.open();
|
|
375
|
+
const id = await (await this.button()).getAttribute("aria-controls");
|
|
376
|
+
const menu = id ? await menuById(this.documentRootLocatorFactory(), id) : null;
|
|
377
|
+
if (!menu) throw new Error("BfMenuButtonHarness: the menu did not open");
|
|
378
|
+
return menu;
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
var BfDesktopToastHarness = class BfDesktopToastHarness extends ComponentHarness {
|
|
382
|
+
static hostSelector = "bf-desktop-toast";
|
|
383
|
+
message = this.locatorFor("span");
|
|
384
|
+
action = this.locatorForOptional("button");
|
|
385
|
+
static with(filters = {}) {
|
|
386
|
+
return new HarnessPredicate(BfDesktopToastHarness, filters).addOption("label", filters.label, async (harness, label) => HarnessPredicate.stringMatches(harness.getMessage(), label));
|
|
387
|
+
}
|
|
388
|
+
async getMessage() {
|
|
389
|
+
return (await (await this.message()).text()).trim();
|
|
390
|
+
}
|
|
391
|
+
async clickAction() {
|
|
392
|
+
const action = await this.action();
|
|
393
|
+
if (!action) throw new Error("BfDesktopToastHarness: the toast has no action");
|
|
394
|
+
await action.click();
|
|
395
|
+
}
|
|
396
|
+
async pressEscape() {
|
|
397
|
+
await (await this.host()).sendKeys(TestKey.ESCAPE);
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
export { BfDesktopToastHarness, BfDialogHarness, BfMenuButtonHarness, BfMenuHarness, BfMenuItemHarness, BfRadioGroupHarness, BfSegmentedHarness, BfSelectHarness, installBfJsdomShims, provideBfTesting };
|
|
401
|
+
|
|
402
|
+
//# sourceMappingURL=bitforgehq-ui-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitforgehq-ui-testing.mjs","names":[],"sources":["../../../projects/ui/testing/jsdom-shims.ts","../../../projects/ui/testing/provide-bf-testing.ts","../../../projects/ui/testing/harnesses/select.harness.ts","../../../projects/ui/testing/harnesses/radio-group.harness.ts","../../../projects/ui/testing/harnesses/segmented.harness.ts","../../../projects/ui/testing/harnesses/dialog.harness.ts","../../../projects/ui/testing/harnesses/menu.harness.ts","../../../projects/ui/testing/harnesses/menu-button.harness.ts","../../../projects/ui/testing/harnesses/desktop-toast.harness.ts"],"sourcesContent":["/**\n * Options of `installBfJsdomShims()`.\n *\n * @summary Options of the jsdom shims: the matchMedia stub and its reduced-motion answer\n * @since 0.3.0\n */\nexport interface BfJsdomShimOptions {\n /**\n * Install a `matchMedia` stub where missing (all queries false).\n * @default true\n */\n matchMedia?: boolean;\n /**\n * With the stub: report `(prefers-reduced-motion: reduce)` as matching, so overlays use their\n * short fade. Has no effect where the environment already has `matchMedia`.\n * @default false\n */\n reducedMotion?: boolean;\n}\n\nconst REDUCED_MOTION_QUERY = /\\(\\s*prefers-reduced-motion\\s*:\\s*reduce\\s*\\)/;\n\n/**\n * A running `Animation` stand-in for jsdom's `Element.prototype.animate`: `finished` resolves at\n * once, and the controls set `playState`. Implements the interface in full (no cast), the same\n * way `BfFinishedAnimation` in `overlay/core/overlay-animations.ts` does for its own, always-done\n * stand-in.\n */\nclass BfShimAnimation extends EventTarget implements Animation {\n currentTime: CSSNumberish | null = 0;\n effect: AnimationEffect | null = null;\n id = '';\n oncancel: ((this: Animation, ev: AnimationPlaybackEvent) => unknown) | null = null;\n onfinish: ((this: Animation, ev: AnimationPlaybackEvent) => unknown) | null = null;\n onremove: ((this: Animation, ev: Event) => unknown) | null = null;\n readonly overallProgress: number | null = 1;\n readonly pending = false;\n playState: AnimationPlayState = 'running';\n playbackRate = 1;\n readonly finished: Promise<Animation> = Promise.resolve(this);\n readonly ready: Promise<Animation> = Promise.resolve(this);\n readonly replaceState: AnimationReplaceState = 'active';\n startTime: CSSNumberish | null = null;\n timeline: AnimationTimeline | null = null;\n\n cancel(): void {\n this.playState = 'idle';\n }\n commitStyles(): void {\n // The end state is already on the element.\n }\n finish(): void {\n this.playState = 'finished';\n }\n pause(): void {\n this.playState = 'paused';\n }\n persist(): void {\n // Nothing to persist.\n }\n play(): void {\n this.playState = 'running';\n }\n reverse(): void {\n // Nothing to reverse.\n }\n updatePlaybackRate(rate: number): void {\n this.playbackRate = rate;\n }\n}\n\n/** A `MouseEvent` carrying the fields of `PointerEventInit`, with the specification's defaults. */\nclass BfPointerEventShim extends MouseEvent {\n readonly pointerId: number;\n readonly isPrimary: boolean;\n readonly pointerType: string;\n readonly width: number;\n readonly height: number;\n readonly pressure: number;\n\n constructor(type: string, init: PointerEventInit = {}) {\n super(type, init);\n this.pointerId = init.pointerId ?? 0;\n this.isPrimary = init.isPrimary ?? false;\n this.pointerType = init.pointerType ?? '';\n this.width = init.width ?? 1;\n this.height = init.height ?? 1;\n this.pressure = init.pressure ?? 0;\n }\n}\n\n/** A `MediaQueryList` stand-in that never changes. */\nfunction staticMediaQueryList(media: string, matches: boolean): MediaQueryList {\n return {\n matches,\n media,\n onchange: null,\n addListener: (): void => undefined,\n removeListener: (): void => undefined,\n addEventListener: (): void => undefined,\n removeEventListener: (): void => undefined,\n dispatchEvent: (): boolean => false,\n };\n}\n\n/**\n * Sets `target[key]` and returns the function that puts back what was there, or `null` when the\n * property cannot be set (then nothing changed). A new property is writable and configurable, so\n * it can be deleted again; a configurable existing one is restored from its descriptor, getters\n * included. An existing non-configurable one (a runner's own `matchMedia` mock, assigned\n * `undefined` to test without it) is assigned through its setter or value and restored the same\n * way; a non-writable one without a setter is left alone. Internal: not exported from the entry.\n */\nexport function installGlobal(target: object, key: string, value: unknown): (() => void) | null {\n const previous = Object.getOwnPropertyDescriptor(target, key);\n if (previous && !previous.configurable) {\n const before: unknown = previous.get ? previous.get.call(target) : previous.value;\n if (!Reflect.set(target, key, value)) {\n return null;\n }\n return () => {\n Reflect.set(target, key, before);\n };\n }\n Object.defineProperty(target, key, { value, writable: true, configurable: true });\n return () => {\n if (previous) {\n Object.defineProperty(target, key, previous);\n } else {\n Reflect.deleteProperty(target, key);\n }\n };\n}\n\n/**\n * Installs, only where missing: `Element.prototype.animate` (an Animation whose `finished`\n * resolves at once), `PointerEvent` (a MouseEvent subclass with pointerId, isPrimary, pointerType,\n * width, height, pressure) and optionally `matchMedia`. Idempotent; returns a function that removes\n * exactly what it installed. Call it once in the test runner's setup file (Vitest `setupFiles`,\n * Jest `setupFilesAfterEnv`).\n *\n * @example\n * // src/test-setup.ts\n * import { installBfJsdomShims } from '@bitforgehq/ui/testing';\n * installBfJsdomShims();\n * @param options Whether to stub `matchMedia`, and whether the stub reports reduced motion.\n * @returns A function that removes the shims this call installed.\n * @summary Installs the browser APIs jsdom lacks and the library needs, only where missing\n * @since 0.3.0\n */\nexport function installBfJsdomShims(options: BfJsdomShimOptions = {}): () => void {\n const removers: (() => void)[] = [];\n const add = (remove: (() => void) | null): void => {\n if (remove) {\n removers.push(remove);\n }\n };\n\n if (typeof Element !== 'undefined' && typeof Element.prototype.animate !== 'function') {\n add(\n installGlobal(\n Element.prototype,\n 'animate',\n function animate(\n _keyframes: Keyframe[] | PropertyIndexedKeyframes | null,\n _timing?: number | KeyframeAnimationOptions,\n ): Animation {\n return new BfShimAnimation();\n },\n ),\n );\n }\n\n if (typeof globalThis.PointerEvent !== 'function' && typeof MouseEvent === 'function') {\n add(installGlobal(globalThis, 'PointerEvent', BfPointerEventShim));\n }\n\n if (\n (options.matchMedia ?? true) &&\n typeof window !== 'undefined' &&\n typeof window.matchMedia !== 'function'\n ) {\n const reducedMotion = options.reducedMotion ?? false;\n const media = (query: string): MediaQueryList =>\n staticMediaQueryList(query, reducedMotion && REDUCED_MOTION_QUERY.test(query));\n add(installGlobal(window, 'matchMedia', media));\n }\n\n return () => {\n for (const remove of removers.splice(0).reverse()) {\n remove();\n }\n };\n}\n","import { type EnvironmentProviders, makeEnvironmentProviders, type Provider } from '@angular/core';\nimport { provideBfDefaults, provideBfLabels, ɵprovideBfDocumentReset } from '@bitforgehq/ui';\n\n/**\n * Options of `provideBfTesting()`.\n *\n * @summary Options of the test providers: the size defaults and labels a spec runs with\n * @since 0.3.0\n */\nexport interface BfTestingOptions {\n /** Passed to `provideBfDefaults()`, e.g. `{ fieldSize: 'sm' }`; left out, the library defaults apply. */\n defaults?: Parameters<typeof provideBfDefaults>[0];\n /** Passed to `provideBfLabels()`, e.g. German validation messages; left out, the English defaults apply. */\n labels?: Parameters<typeof provideBfLabels>[0];\n}\n\n/**\n * Test providers for apps using the root and overlay entries: passes `defaults`/`labels` to 3b's\n * providers, and on environment teardown removes what overlays and themes leave on the document —\n * `dark` and `cdk-global-scrollblock` on `<html>`, `inert` on body children, stray\n * `.cdk-overlay-container` children. The cleanup runs when TestBed destroys the test environment,\n * so it needs TestBed's default `teardown: { destroyAfterEach: true }`; with `destroyAfterEach:\n * false` nothing is cleaned between tests. Mobile shell apps use `provideBfShellTesting()` from\n * `@bitforgehq/ui/mobile` instead.\n *\n * @example\n * TestBed.configureTestingModule({\n * providers: [provideBfTesting({ defaults: { fieldSize: 'sm' } })],\n * });\n * @param options The size defaults and labels to provide.\n * @returns The environment providers for `TestBed.configureTestingModule()`.\n * @summary Test providers for the widgets and overlays, with document cleanup after each test\n * @since 0.3.0\n */\nexport function provideBfTesting(options: BfTestingOptions = {}): EnvironmentProviders {\n const providers: Provider[] = [];\n if (options.defaults) {\n providers.push(provideBfDefaults(options.defaults));\n }\n if (options.labels) {\n providers.push(provideBfLabels(options.labels));\n }\n return makeEnvironmentProviders([...providers, ɵprovideBfDocumentReset()]);\n}\n","import { ComponentHarness, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\n\n/** A rendered native option and whether it is one of the select's `options` (not the placeholder). */\ninterface NativeOption {\n element: TestElement;\n label: string;\n choice: boolean;\n}\n\n/**\n * Harness for `bf-select`. It works by option label, so a test never needs the native option\n * values (an object option's native value is its index).\n *\n * @example\n * const select = await loader.getHarness(BfSelectHarness.with({ label: 'Project' }));\n * await select.selectOption('Two');\n * @summary Test harness for the select: reads its label and options and picks one\n * @since 0.3.0\n */\nexport class BfSelectHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-select';\n\n private readonly select = this.locatorFor('select');\n private readonly label = this.locatorForOptional('label');\n private readonly error = this.locatorForOptional('p[id$=\"-error\"]');\n private readonly help = this.locatorForOptional('p[id$=\"-help\"]');\n private readonly nativeOptions = this.locatorForAll('option');\n\n /**\n * A predicate for a select with the given label.\n * @param filters The label (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfSelectHarness> {\n return new HarnessPredicate(BfSelectHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label),\n );\n }\n\n /**\n * The visible label without the required mark, else the `aria-label`.\n * @returns The label text.\n */\n async getLabel(): Promise<string> {\n const label = await this.label();\n if (label) {\n return (await label.text({ exclude: '[aria-hidden=\"true\"]' })).trim();\n }\n return (await (await this.select()).getAttribute('aria-label')) ?? '';\n }\n\n /**\n * The selected option's label, `''` while nothing is selected.\n * @returns The label text.\n */\n async getValueLabel(): Promise<string> {\n for (const option of await this.options()) {\n if (option.choice && (await option.element.getProperty<boolean>('selected'))) {\n return option.label;\n }\n }\n return '';\n }\n\n /**\n * The labels of the options, in order, without the placeholder.\n * @returns The option labels.\n */\n async getOptionLabels(): Promise<string[]> {\n return (await this.options()).filter((option) => option.choice).map((option) => option.label);\n }\n\n /**\n * Chooses the option with this label, as a user would: the select's `value` becomes that\n * option's value. Throws, leaving the value as it is, for an unknown or disabled option and\n * while the select itself is disabled.\n * @param label The option's label.\n */\n async selectOption(label: string): Promise<void> {\n if (await this.isDisabled()) {\n throw new Error(`BfSelectHarness: the select is disabled, so \"${label}\" cannot be chosen`);\n }\n const options = await this.options();\n const option = options.find((o) => o.choice && o.label === label);\n if (!option) {\n const known = options.filter((o) => o.choice).map((o) => o.label);\n throw new Error(`BfSelectHarness: no option \"${label}\" (options: ${known.join(', ')})`);\n }\n if (await option.element.getProperty<boolean>('disabled')) {\n throw new Error(`BfSelectHarness: the option \"${label}\" is disabled`);\n }\n // Not `TestElement.selectOptions()`: it deselects the current option first and fires `change`\n // for that too, so the select would first see the browser's fallback option. A user's pick is\n // one `change` with the new option selected.\n const select = await this.select();\n await select.setInputValue(await option.element.getProperty<string>('value'));\n await select.dispatchEvent('change');\n }\n\n /**\n * Whether the select is disabled.\n * @returns True when disabled.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.select()).getProperty<boolean>('disabled');\n }\n\n /**\n * Whether the select is read-only (`aria-readonly`).\n * @returns True when read-only.\n */\n async isReadonly(): Promise<boolean> {\n return (await (await this.select()).getAttribute('aria-readonly')) === 'true';\n }\n\n /**\n * Whether the select is required.\n * @returns True when required.\n */\n async isRequired(): Promise<boolean> {\n return (await this.select()).getProperty<boolean>('required');\n }\n\n /**\n * The error text under the select (an explicit message, or the first form error once touched).\n * @returns The error text, `''` when there is none.\n */\n async getErrorText(): Promise<string> {\n return (await (await this.error())?.text())?.trim() ?? '';\n }\n\n /**\n * The help text under the select; it is hidden while an error shows.\n * @returns The help text, `''` when there is none.\n */\n async getHelpText(): Promise<string> {\n return (await (await this.help())?.text())?.trim() ?? '';\n }\n\n /** Focuses the native select. */\n async focus(): Promise<void> {\n await (await this.select()).focus();\n }\n\n /** Blurs the native select, which marks a bound form field touched. */\n async blur(): Promise<void> {\n await (await this.select()).blur();\n }\n\n /** Every native option; the placeholder or hidden empty option comes first, empty and disabled. */\n private async options(): Promise<NativeOption[]> {\n return Promise.all(\n (await this.nativeOptions()).map(async (element, index) => {\n const [label, value, hidden, disabled] = await Promise.all([\n element.text(),\n element.getAttribute('value'),\n element.getProperty<boolean>('hidden'),\n element.getProperty<boolean>('disabled'),\n ]);\n const placeholder = index === 0 && (hidden || (value === '' && disabled));\n return { element, label: label.trim(), choice: !placeholder };\n }),\n );\n }\n}\n","import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\n\n/**\n * Harness for `bf-radio-group`. It works by option label, so a test never needs the native radio\n * values (an object option's native value is its index).\n *\n * @example\n * const plan = await loader.getHarness(BfRadioGroupHarness.with({ label: 'Plan' }));\n * await plan.select('Team');\n * @summary Test harness for the radio group: reads its options and selects one by label\n * @since 0.3.0\n */\nexport class BfRadioGroupHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-radio-group';\n\n private readonly fieldset = this.locatorFor('fieldset');\n private readonly legend = this.locatorForOptional('legend');\n private readonly error = this.locatorForOptional('p[id$=\"-error\"]');\n private readonly radios = this.locatorForAll('[data-options] > label input[type=\"radio\"]');\n private readonly labels = this.locatorForAll(\n '[data-options] > label > span:last-child > span:first-child',\n );\n\n /**\n * A predicate for a radio group with the given label.\n * @param filters The label (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfRadioGroupHarness> {\n return new HarnessPredicate(BfRadioGroupHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label),\n );\n }\n\n /**\n * The legend, else the group's `aria-label`.\n * @returns The label text.\n */\n async getLabel(): Promise<string> {\n const legend = await this.legend();\n if (legend) {\n return (await legend.text()).trim();\n }\n return (await (await this.fieldset()).getAttribute('aria-label')) ?? '';\n }\n\n /**\n * The labels of the options, in order.\n * @returns The option labels.\n */\n async getOptionLabels(): Promise<string[]> {\n return Promise.all((await this.labels()).map(async (label) => (await label.text()).trim()));\n }\n\n /**\n * The selected option's label, `null` while nothing is selected.\n * @returns The label text or `null`.\n */\n async getSelectedLabel(): Promise<string | null> {\n const [radios, labels] = await Promise.all([this.radios(), this.getOptionLabels()]);\n for (const [index, radio] of radios.entries()) {\n if (await radio.getProperty<boolean>('checked')) {\n return labels[index] ?? null;\n }\n }\n return null;\n }\n\n /**\n * Clicks the option with this label, as a user would. Throws for an unknown label, and for a\n * disabled option or group (a user's click would do nothing).\n * @param label The option's label.\n */\n async select(label: string): Promise<void> {\n const [radios, labels] = await Promise.all([this.radios(), this.getOptionLabels()]);\n const radio = radios[labels.indexOf(label)];\n if (!radio) {\n throw new Error(`BfRadioGroupHarness: no option \"${label}\" (options: ${labels.join(', ')})`);\n }\n // `:disabled` also covers a radio inside the disabled fieldset of a disabled group.\n if (await radio.matchesSelector(':disabled')) {\n throw new Error(`BfRadioGroupHarness: the option \"${label}\" is disabled`);\n }\n await radio.click();\n }\n\n /**\n * Whether the whole group is disabled.\n * @returns True when disabled.\n */\n async isDisabled(): Promise<boolean> {\n return (await this.fieldset()).getProperty<boolean>('disabled');\n }\n\n /**\n * The error text under the options.\n * @returns The error text, `''` when there is none.\n */\n async getErrorText(): Promise<string> {\n return (await (await this.error())?.text())?.trim() ?? '';\n }\n}\n","import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\n\n/**\n * Harness for `bf-segmented`. It works by segment label, so a test never needs the option values.\n *\n * @example\n * const range = await loader.getHarness(BfSegmentedHarness.with({ label: 'Range' }));\n * await range.select('Week');\n * @summary Test harness for the segmented control: reads its segments and selects one\n * @since 0.3.0\n */\nexport class BfSegmentedHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-segmented';\n\n private readonly group = this.locatorFor('[role=\"radiogroup\"]');\n private readonly error = this.locatorForOptional('p[id$=\"-error\"]');\n private readonly segments = this.locatorForAll('[role=\"radio\"]');\n\n /**\n * A predicate for a segmented control with the given label.\n * @param filters The label (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfSegmentedHarness> {\n return new HarnessPredicate(BfSegmentedHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label),\n );\n }\n\n /**\n * The group's accessible name (`label`, else `ariaLabel`); it is not rendered visibly.\n * @returns The label text.\n */\n async getLabel(): Promise<string> {\n return (await (await this.group()).getAttribute('aria-label')) ?? '';\n }\n\n /**\n * The labels of the segments, in order.\n * @returns The segment labels.\n */\n async getOptionLabels(): Promise<string[]> {\n return Promise.all(\n (await this.segments()).map(async (segment) => (await segment.text()).trim()),\n );\n }\n\n /**\n * The selected segment's label, `null` while nothing is selected.\n * @returns The label text or `null`.\n */\n async getSelectedLabel(): Promise<string | null> {\n for (const segment of await this.segments()) {\n if ((await segment.getAttribute('aria-checked')) === 'true') {\n return (await segment.text()).trim();\n }\n }\n return null;\n }\n\n /**\n * Clicks the segment with this label, as a user would. Throws for an unknown label, and for a\n * disabled segment or control (a user's click would do nothing).\n * @param label The segment's label.\n */\n async select(label: string): Promise<void> {\n const [segments, labels] = await Promise.all([this.segments(), this.getOptionLabels()]);\n const segment = segments[labels.indexOf(label)];\n if (!segment) {\n throw new Error(`BfSegmentedHarness: no segment \"${label}\" (segments: ${labels.join(', ')})`);\n }\n if (await segment.matchesSelector(':disabled')) {\n throw new Error(`BfSegmentedHarness: the segment \"${label}\" is disabled`);\n }\n await segment.click();\n }\n\n /**\n * Whether the control is disabled: every segment is.\n * @returns True when disabled.\n */\n async isDisabled(): Promise<boolean> {\n const segments = await this.segments();\n const disabled = await Promise.all(\n segments.map((segment) => segment.getProperty<boolean>('disabled')),\n );\n return segments.length > 0 && disabled.every(Boolean);\n }\n\n /**\n * The error text under the track.\n * @returns The error text, `''` when there is none.\n */\n async getErrorText(): Promise<string> {\n return (await (await this.error())?.text())?.trim() ?? '';\n }\n}\n","import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\n\n/**\n * Harness for `bf-dialog`, the card `BfDialogService` opens (a `confirm()` alert dialog is built\n * from it too). Dialogs render in the CDK overlay container outside the tested component, so get\n * it through `TestbedHarnessEnvironment.documentRootLoader(fixture)`.\n *\n * @example\n * const dialog = await TestbedHarnessEnvironment.documentRootLoader(fixture).getHarness(\n * BfDialogHarness.with({ label: 'Delete project?' }),\n * );\n * await dialog.clickButton('Delete');\n * @summary Test harness for a dialog: its title, text and role, its buttons and Escape\n * @since 0.3.0\n */\nexport class BfDialogHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-dialog';\n\n private readonly title = this.locatorForOptional('[data-dialog-header] h2');\n private readonly body = this.locatorFor('[data-dialog-body]');\n private readonly closeButton = this.locatorForOptional('[data-dialog-close] button');\n private readonly buttons = this.locatorForAll('button');\n\n /**\n * A predicate for a dialog with the given title.\n * @param filters The title (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfDialogHarness> {\n return new HarnessPredicate(BfDialogHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getTitle(), label),\n );\n }\n\n /**\n * The title heading.\n * @returns The title, `''` without one.\n */\n async getTitle(): Promise<string> {\n return (await (await this.title())?.text())?.trim() ?? '';\n }\n\n /**\n * The text of the dialog's body (its content, without the title and footer).\n * @returns The body text.\n */\n async getText(): Promise<string> {\n return (await (await this.body()).text()).trim();\n }\n\n /**\n * The role of the overlay panel this dialog sits in: `dialog`, or `alertdialog` for\n * `confirm()` and `role: 'alertdialog'`. The panel is found by the dialog's own\n * `data-bf-dialog-id`, so a dialog under another one (a confirm opened from a dialog) reads its\n * own panel.\n * @returns The role, `null` outside `BfDialogService` (a `bf-dialog` rendered in the page).\n */\n async getRole(): Promise<string | null> {\n const id = await (await this.host()).getAttribute('data-bf-dialog-id');\n if (!id) {\n return null;\n }\n const root = this.documentRootLocatorFactory();\n for (const panel of await root.locatorForAll('.bf-overlay-panel[id]')()) {\n const paneId = await panel.getAttribute('id');\n const own = await root.locatorForOptional(\n `[id=\"${paneId}\"] bf-dialog[data-bf-dialog-id=\"${id}\"]`,\n )();\n if (own) {\n return panel.getAttribute('role');\n }\n }\n return null;\n }\n\n /**\n * Clicks the button with this text (a footer or content button). Throws for an unknown label.\n * @param label The button's text.\n */\n async clickButton(label: string): Promise<void> {\n const buttons = await this.buttons();\n const texts = await Promise.all(buttons.map(async (button) => (await button.text()).trim()));\n const button = buttons[texts.indexOf(label)];\n if (!button) {\n const known = texts.filter(Boolean);\n throw new Error(`BfDialogHarness: no button \"${label}\" (buttons: ${known.join(', ')})`);\n }\n await button.click();\n }\n\n /** Clicks the close button (×), which closes the dialog with no result. Throws without one. */\n async close(): Promise<void> {\n const button = await this.closeButton();\n if (!button) {\n throw new Error('BfDialogHarness: the dialog has no close button');\n }\n await button.click();\n }\n\n /** Presses `Escape` inside the dialog: it closes, or a `confirm()` resolves its cancel answer. */\n async pressEscape(): Promise<void> {\n await (await this.body()).sendKeys(TestKey.ESCAPE);\n }\n}\n","import { ComponentHarness, HarnessPredicate, type LocatorFactory } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\n\n/**\n * Harness for one row of a `bf-menu`: a `bfMenuItem`, `bfMenuItemCheckbox` or `bfMenuItemRadio`\n * button (or `a[bfMenuItem]` link). Its `label` filter matches the row's text.\n *\n * @example\n * const [first] = await menu.getItems({ label: 'Duplicate' });\n * expect(await first.isDisabled()).toBe(false);\n * @summary Test harness for a menu row: its text, disabled, checked and submenu state\n * @since 0.3.0\n */\nexport class BfMenuItemHarness extends ComponentHarness {\n /** The host element's selector: the menu item directives' selectors. */\n static hostSelector =\n 'button[bfMenuItem], a[bfMenuItem], button[bfMenuItemCheckbox], button[bfMenuItemRadio]';\n\n /**\n * A predicate for a menu item with the given text.\n * @param filters The text (exact or a pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfMenuItemHarness> {\n return new HarnessPredicate(BfMenuItemHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getText(), label),\n );\n }\n\n /**\n * The row's text.\n * @returns The text.\n */\n async getText(): Promise<string> {\n return (await (await this.host()).text()).trim();\n }\n\n /**\n * Whether the row is disabled (`aria-disabled`; a disabled row stays focusable).\n * @returns True when disabled.\n */\n async isDisabled(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-disabled')) === 'true';\n }\n\n /**\n * Whether a checkbox or radio row is checked.\n * @returns True when checked.\n */\n async isChecked(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-checked')) === 'true';\n }\n\n /**\n * Whether the row opens a submenu.\n * @returns True when it has a submenu.\n */\n async hasSubmenu(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-haspopup')) === 'menu';\n }\n\n /** Clicks the row: runs its action (closing the menu), toggles it, or opens its submenu. */\n async click(): Promise<void> {\n await (await this.host()).click();\n }\n\n /**\n * The submenu this row opened, or `null` while it is closed or when it has none.\n * @returns The submenu's harness or `null`.\n */\n async getSubmenu(): Promise<BfMenuHarness | null> {\n const id = await (await this.host()).getAttribute('aria-controls');\n return id ? menuById(this.documentRootLocatorFactory(), id) : null;\n }\n}\n\n/**\n * Harness for `bf-menu`, the card a `bfMenuTriggerFor` trigger or `bf-menu-button` opens. It\n * renders in the CDK overlay container, so get it from `BfMenuButtonHarness.getMenu()` or through\n * `TestbedHarnessEnvironment.documentRootLoader(fixture)`. Its `label` filter matches the menu's\n * accessible name (its `ariaLabel`, else its trigger's text).\n *\n * @example\n * const menu = await (await loader.getHarness(BfMenuButtonHarness)).getMenu();\n * await menu.clickItem('Move to', 'Archive');\n * @summary Test harness for a menu: lists its items and clicks one, through submenus\n * @since 0.3.0\n */\nexport class BfMenuHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-menu';\n\n /**\n * A predicate for a menu with the given accessible name.\n * @param filters The name (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfMenuHarness> {\n return new HarnessPredicate(BfMenuHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label),\n );\n }\n\n /**\n * The menu's accessible name: its `aria-label`, else the text of the element it is labelled\n * by (its trigger).\n * @returns The name, `''` without one.\n */\n async getLabel(): Promise<string> {\n const host = await this.host();\n const label = await host.getAttribute('aria-label');\n if (label) {\n return label;\n }\n const by = await host.getAttribute('aria-labelledby');\n const named = by\n ? await this.documentRootLocatorFactory().locatorForOptional(`[id=\"${by}\"]`)()\n : null;\n return (await named?.text())?.trim() ?? '';\n }\n\n /**\n * The menu's rows, in order (a submenu's rows belong to the submenu).\n * @param filter Only the rows matching it, e.g. `{ label: 'Archive' }`.\n * @returns The row harnesses.\n */\n async getItems(filter: BfHarnessFilters = {}): Promise<BfMenuItemHarness[]> {\n return this.locatorForAll(BfMenuItemHarness.with(filter))();\n }\n\n /**\n * Clicks the row with this text; with a `submenuPath`, the row opens a submenu and the path's\n * labels are clicked in the submenus that open, in order. Throws for an unknown label.\n * @param label The row's text.\n * @param submenuPath The labels to click in the submenus that open, in order.\n */\n async clickItem(label: string, ...submenuPath: string[]): Promise<void> {\n const [item] = await this.getItems({ label });\n if (!item) {\n const known = await Promise.all((await this.getItems()).map((i) => i.getText()));\n throw new Error(`BfMenuHarness: no item \"${label}\" (items: ${known.join(', ')})`);\n }\n await item.click();\n const [next, ...rest] = submenuPath;\n if (next === undefined) {\n return;\n }\n const submenu = await item.getSubmenu();\n if (!submenu) {\n throw new Error(`BfMenuHarness: the item \"${label}\" opened no submenu`);\n }\n await submenu.clickItem(next, ...rest);\n }\n}\n\n/** The `bf-menu` with this id in the document, or `null`. Internal. */\nexport async function menuById(root: LocatorFactory, id: string): Promise<BfMenuHarness | null> {\n return root.locatorForOptional(BfMenuHarness.with({ selector: `[id=\"${id}\"]` }))();\n}\n","import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\nimport { type BfMenuHarness, menuById } from './menu.harness';\n\n/**\n * Harness for `bf-menu-button`, a button that opens a `bf-menu`. Its `label` filter matches the\n * button's accessible name (its `label`, else its text).\n *\n * @example\n * const button = await loader.getHarness(BfMenuButtonHarness.with({ label: 'Actions' }));\n * await (await button.getMenu()).clickItem('Archive');\n * @summary Test harness for a menu button: opens it and hands out the menu it opened\n * @since 0.3.0\n */\nexport class BfMenuButtonHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-menu-button';\n\n private readonly button = this.locatorFor('button');\n\n /**\n * A predicate for a menu button with the given accessible name.\n * @param filters The name (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfMenuButtonHarness> {\n return new HarnessPredicate(BfMenuButtonHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getLabel(), label),\n );\n }\n\n /**\n * The button's accessible name: its `aria-label` (the `label` input), else its text.\n * @returns The name.\n */\n async getLabel(): Promise<string> {\n const button = await this.button();\n return (await button.getAttribute('aria-label')) ?? (await button.text()).trim();\n }\n\n /** Opens the menu with a click; does nothing while it is open. */\n async open(): Promise<void> {\n if (!(await this.isOpen())) {\n await (await this.button()).click();\n }\n }\n\n /**\n * Whether the menu is open (`aria-expanded`).\n * @returns True while open.\n */\n async isOpen(): Promise<boolean> {\n return (await (await this.button()).getAttribute('aria-expanded')) === 'true';\n }\n\n /**\n * The open menu, opening it first when it is closed.\n * @returns The menu's harness.\n */\n async getMenu(): Promise<BfMenuHarness> {\n await this.open();\n const id = await (await this.button()).getAttribute('aria-controls');\n const menu = id ? await menuById(this.documentRootLocatorFactory(), id) : null;\n if (!menu) {\n throw new Error('BfMenuButtonHarness: the menu did not open');\n }\n return menu;\n }\n}\n","import { ComponentHarness, HarnessPredicate, TestKey } from '@angular/cdk/testing';\nimport { type BfHarnessFilters } from './harness-filters';\n\n/**\n * Harness for `bf-desktop-toast`, the card `BfDesktopToastService.show()` renders. Toasts render\n * in the CDK overlay container, so get it through\n * `TestbedHarnessEnvironment.documentRootLoader(fixture)`.\n *\n * @example\n * const toast = await TestbedHarnessEnvironment.documentRootLoader(fixture).getHarness(\n * BfDesktopToastHarness,\n * );\n * await toast.clickAction();\n * @summary Test harness for a desktop toast: its message, its action and Escape\n * @since 0.3.0\n */\nexport class BfDesktopToastHarness extends ComponentHarness {\n /** The host element's selector. */\n static hostSelector = 'bf-desktop-toast';\n\n private readonly message = this.locatorFor('span');\n private readonly action = this.locatorForOptional('button');\n\n /**\n * A predicate for a toast with the given message.\n * @param filters The message (text or pattern) and the CDK's selector and ancestor filters.\n * @returns The predicate for `getHarness()` and friends.\n */\n static with(filters: BfHarnessFilters = {}): HarnessPredicate<BfDesktopToastHarness> {\n return new HarnessPredicate(BfDesktopToastHarness, filters).addOption(\n 'label',\n filters.label,\n async (harness, label) => HarnessPredicate.stringMatches(harness.getMessage(), label),\n );\n }\n\n /**\n * The toast's message.\n * @returns The message text.\n */\n async getMessage(): Promise<string> {\n return (await (await this.message()).text()).trim();\n }\n\n /** Clicks the action button, which dismisses the toast with `'action'`. Throws without one. */\n async clickAction(): Promise<void> {\n const action = await this.action();\n if (!action) {\n throw new Error('BfDesktopToastHarness: the toast has no action');\n }\n await action.click();\n }\n\n /** Presses `Escape` inside the toast, which dismisses it with `'dismiss'`. */\n async pressEscape(): Promise<void> {\n await (await this.host()).sendKeys(TestKey.ESCAPE);\n }\n}\n"],"mappings":"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"}
|