ngwr 11.0.0 → 11.2.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/action-sheet/styles/_index.scss +4 -1
- package/anchor/styles/_index.scss +2 -1
- package/calendar/styles/_index.scss +11 -2
- package/cascader/styles/_index.scss +3 -1
- package/circular-text/styles/_index.scss +11 -0
- package/command-palette/styles/_index.scss +14 -1
- package/context-menu/styles/_index.scss +2 -1
- package/falling-text/styles/_index.scss +2 -1
- package/fesm2022/ngwr-action-sheet-testing.mjs +313 -0
- package/fesm2022/ngwr-action-sheet-testing.mjs.map +1 -0
- package/fesm2022/ngwr-bar-chart-testing.mjs +89 -0
- package/fesm2022/ngwr-bar-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-blur-text-testing.mjs +203 -0
- package/fesm2022/ngwr-blur-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-blur-text.mjs +15 -1
- package/fesm2022/ngwr-blur-text.mjs.map +1 -1
- package/fesm2022/ngwr-border-glow-testing.mjs +325 -0
- package/fesm2022/ngwr-border-glow-testing.mjs.map +1 -0
- package/fesm2022/ngwr-border-glow.mjs +2 -2
- package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs +95 -0
- package/fesm2022/ngwr-calendar-heatmap-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar-testing.mjs +368 -0
- package/fesm2022/ngwr-calendar-testing.mjs.map +1 -0
- package/fesm2022/ngwr-calendar.mjs +43 -3
- package/fesm2022/ngwr-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-card.mjs +2 -2
- package/fesm2022/ngwr-cascader.mjs +14 -2
- package/fesm2022/ngwr-cascader.mjs.map +1 -1
- package/fesm2022/ngwr-checkbox.mjs +8 -0
- package/fesm2022/ngwr-checkbox.mjs.map +1 -1
- package/fesm2022/ngwr-circular-text-testing.mjs +230 -0
- package/fesm2022/ngwr-circular-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-circular-text.mjs +2 -2
- package/fesm2022/ngwr-circular-text.mjs.map +1 -1
- package/fesm2022/ngwr-color-picker-testing.mjs +432 -0
- package/fesm2022/ngwr-color-picker-testing.mjs.map +1 -0
- package/fesm2022/ngwr-color-picker.mjs +29 -3
- package/fesm2022/ngwr-color-picker.mjs.map +1 -1
- package/fesm2022/ngwr-context-menu.mjs +25 -5
- package/fesm2022/ngwr-context-menu.mjs.map +1 -1
- package/fesm2022/ngwr-date-picker-testing.mjs +10 -79
- package/fesm2022/ngwr-date-picker-testing.mjs.map +1 -1
- package/fesm2022/ngwr-decrypt-text-testing.mjs +261 -0
- package/fesm2022/ngwr-decrypt-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-decrypt-text.mjs +6 -1
- package/fesm2022/ngwr-decrypt-text.mjs.map +1 -1
- package/fesm2022/ngwr-donut-chart-testing.mjs +93 -0
- package/fesm2022/ngwr-donut-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-drag-drop.mjs +2 -2
- package/fesm2022/ngwr-drawer.mjs +36 -7
- package/fesm2022/ngwr-drawer.mjs.map +1 -1
- package/fesm2022/ngwr-dropdown.mjs +32 -2
- package/fesm2022/ngwr-dropdown.mjs.map +1 -1
- package/fesm2022/ngwr-event-calendar-testing.mjs +446 -0
- package/fesm2022/ngwr-event-calendar-testing.mjs.map +1 -0
- package/fesm2022/ngwr-event-calendar.mjs +43 -18
- package/fesm2022/ngwr-event-calendar.mjs.map +1 -1
- package/fesm2022/ngwr-falling-text-testing.mjs +218 -0
- package/fesm2022/ngwr-falling-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-falling-text.mjs +2 -2
- package/fesm2022/ngwr-falling-text.mjs.map +1 -1
- package/fesm2022/ngwr-fuzzy-text-testing.mjs +134 -0
- package/fesm2022/ngwr-fuzzy-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-gauge-testing.mjs +86 -0
- package/fesm2022/ngwr-gauge-testing.mjs.map +1 -0
- package/fesm2022/ngwr-glitch-text-testing.mjs +181 -0
- package/fesm2022/ngwr-glitch-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-glitch-text.mjs +2 -2
- package/fesm2022/ngwr-gradient-text-testing.mjs +223 -0
- package/fesm2022/ngwr-gradient-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-gradient-text.mjs +2 -2
- package/fesm2022/ngwr-i18n-en.mjs +14 -0
- package/fesm2022/ngwr-i18n-en.mjs.map +1 -1
- package/fesm2022/ngwr-i18n-ru.mjs +14 -0
- package/fesm2022/ngwr-i18n-ru.mjs.map +1 -1
- package/fesm2022/ngwr-image-cropper-testing.mjs +156 -0
- package/fesm2022/ngwr-image-cropper-testing.mjs.map +1 -0
- package/fesm2022/ngwr-image-cropper.mjs +10 -3
- package/fesm2022/ngwr-image-cropper.mjs.map +1 -1
- package/fesm2022/ngwr-input-number.mjs +73 -6
- package/fesm2022/ngwr-input-number.mjs.map +1 -1
- package/fesm2022/ngwr-knob-testing.mjs +220 -0
- package/fesm2022/ngwr-knob-testing.mjs.map +1 -0
- package/fesm2022/ngwr-lightbox-testing.mjs +223 -0
- package/fesm2022/ngwr-lightbox-testing.mjs.map +1 -0
- package/fesm2022/ngwr-lightbox.mjs +9 -2
- package/fesm2022/ngwr-lightbox.mjs.map +1 -1
- package/fesm2022/ngwr-line-chart-testing.mjs +119 -0
- package/fesm2022/ngwr-line-chart-testing.mjs.map +1 -0
- package/fesm2022/ngwr-list.mjs +2 -2
- package/fesm2022/ngwr-loading-bar.mjs +2 -2
- package/fesm2022/ngwr-marquee-testing.mjs +336 -0
- package/fesm2022/ngwr-marquee-testing.mjs.map +1 -0
- package/fesm2022/ngwr-marquee.mjs +2 -2
- package/fesm2022/ngwr-meter-group-testing.mjs +80 -0
- package/fesm2022/ngwr-meter-group-testing.mjs.map +1 -0
- package/fesm2022/ngwr-overlay.mjs +28 -1
- package/fesm2022/ngwr-overlay.mjs.map +1 -1
- package/fesm2022/ngwr-popover.mjs +14 -0
- package/fesm2022/ngwr-popover.mjs.map +1 -1
- package/fesm2022/ngwr-rotating-text-testing.mjs +220 -0
- package/fesm2022/ngwr-rotating-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-rotating-text.mjs +2 -2
- package/fesm2022/ngwr-rotating-text.mjs.map +1 -1
- package/fesm2022/ngwr-select.mjs +70 -11
- package/fesm2022/ngwr-select.mjs.map +1 -1
- package/fesm2022/ngwr-shiny-text-testing.mjs +237 -0
- package/fesm2022/ngwr-shiny-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-shiny-text.mjs +2 -2
- package/fesm2022/ngwr-slider.mjs +44 -6
- package/fesm2022/ngwr-slider.mjs.map +1 -1
- package/fesm2022/ngwr-sparkline-testing.mjs +76 -0
- package/fesm2022/ngwr-sparkline-testing.mjs.map +1 -0
- package/fesm2022/ngwr-speed-dial-testing.mjs +279 -0
- package/fesm2022/ngwr-speed-dial-testing.mjs.map +1 -0
- package/fesm2022/ngwr-splash-cursor-testing.mjs +89 -0
- package/fesm2022/ngwr-splash-cursor-testing.mjs.map +1 -0
- package/fesm2022/ngwr-split-text-testing.mjs +224 -0
- package/fesm2022/ngwr-split-text-testing.mjs.map +1 -0
- package/fesm2022/ngwr-split-text.mjs +15 -1
- package/fesm2022/ngwr-split-text.mjs.map +1 -1
- package/fesm2022/ngwr-splitter-testing.mjs +245 -0
- package/fesm2022/ngwr-splitter-testing.mjs.map +1 -0
- package/fesm2022/ngwr-spotlight-card-testing.mjs +368 -0
- package/fesm2022/ngwr-spotlight-card-testing.mjs.map +1 -0
- package/fesm2022/ngwr-spotlight-card.mjs +2 -2
- package/fesm2022/ngwr-star-border-testing.mjs +172 -0
- package/fesm2022/ngwr-star-border-testing.mjs.map +1 -0
- package/fesm2022/ngwr-star-border.mjs +2 -2
- package/fesm2022/ngwr-table.mjs +13 -3
- package/fesm2022/ngwr-table.mjs.map +1 -1
- package/fesm2022/ngwr-tabs.mjs +15 -1
- package/fesm2022/ngwr-tabs.mjs.map +1 -1
- package/fesm2022/ngwr-tilt-card-testing.mjs +190 -0
- package/fesm2022/ngwr-tilt-card-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tilt-card.mjs +21 -3
- package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
- package/fesm2022/ngwr-toast.mjs +36 -7
- package/fesm2022/ngwr-toast.mjs.map +1 -1
- package/fesm2022/ngwr-tour-testing.mjs +173 -0
- package/fesm2022/ngwr-tour-testing.mjs.map +1 -0
- package/fesm2022/ngwr-tour.mjs +2 -2
- package/fesm2022/ngwr-tour.mjs.map +1 -1
- package/fesm2022/ngwr-tree.mjs +10 -3
- package/fesm2022/ngwr-tree.mjs.map +1 -1
- package/fesm2022/ngwr-typewriter-testing.mjs +228 -0
- package/fesm2022/ngwr-typewriter-testing.mjs.map +1 -0
- package/fesm2022/ngwr-typewriter.mjs +2 -2
- package/fesm2022/ngwr-version.mjs +1 -1
- package/fesm2022/ngwr-version.mjs.map +1 -1
- package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
- package/fesm2022/ngwr-waves-testing.mjs +128 -0
- package/fesm2022/ngwr-waves-testing.mjs.map +1 -0
- package/fesm2022/ngwr-waves.mjs +2 -2
- package/fesm2022/ngwr-window-testing.mjs +361 -0
- package/fesm2022/ngwr-window-testing.mjs.map +1 -0
- package/fesm2022/ngwr-window.mjs +84 -10
- package/fesm2022/ngwr-window.mjs.map +1 -1
- package/file-upload/styles/_index.scss +7 -2
- package/i18n/en.json +16 -2
- package/i18n/ru.json +16 -2
- package/llms-full.txt +147 -0
- package/llms.txt +27 -8
- package/mcp/providers.js +29 -0
- package/mcp/tools.js +1 -29
- package/package.json +145 -1
- package/result/styles/_index.scss +3 -1
- package/segmented/styles/_index.scss +5 -1
- package/select/styles/_index.scss +3 -1
- package/skills/ngwr/SKILL.md +115 -0
- package/skills/ngwr/references/catalog.md +212 -0
- package/skills/ngwr/references/setup.md +73 -0
- package/stepper/styles/_index.scss +18 -2
- package/table/styles/_index.scss +3 -1
- package/theme/styles/_colors.scss +15 -2
- package/theme/styles/_dark.scss +24 -12
- package/tree/styles/_index.scss +4 -2
- package/types/ngwr-action-sheet-testing.d.ts +260 -0
- package/types/ngwr-bar-chart-testing.d.ts +77 -0
- package/types/ngwr-blur-text-testing.d.ts +180 -0
- package/types/ngwr-blur-text.d.ts +12 -0
- package/types/ngwr-border-glow-testing.d.ts +219 -0
- package/types/ngwr-calendar-heatmap-testing.d.ts +79 -0
- package/types/ngwr-calendar-testing.d.ts +277 -0
- package/types/ngwr-calendar.d.ts +10 -0
- package/types/ngwr-checkbox.d.ts +13 -0
- package/types/ngwr-circular-text-testing.d.ts +196 -0
- package/types/ngwr-color-picker-testing.d.ts +291 -0
- package/types/ngwr-color-picker.d.ts +13 -1
- package/types/ngwr-context-menu.d.ts +15 -1
- package/types/ngwr-date-picker-testing.d.ts +6 -66
- package/types/ngwr-decrypt-text-testing.d.ts +222 -0
- package/types/ngwr-donut-chart-testing.d.ts +79 -0
- package/types/ngwr-drawer.d.ts +12 -0
- package/types/ngwr-dropdown.d.ts +9 -0
- package/types/ngwr-event-calendar-testing.d.ts +301 -0
- package/types/ngwr-event-calendar.d.ts +9 -6
- package/types/ngwr-falling-text-testing.d.ts +186 -0
- package/types/ngwr-fuzzy-text-testing.d.ts +130 -0
- package/types/ngwr-gauge-testing.d.ts +71 -0
- package/types/ngwr-glitch-text-testing.d.ts +172 -0
- package/types/ngwr-gradient-text-testing.d.ts +173 -0
- package/types/ngwr-image-cropper-testing.d.ts +134 -0
- package/types/ngwr-image-cropper.d.ts +1 -0
- package/types/ngwr-input-number.d.ts +33 -1
- package/types/ngwr-knob-testing.d.ts +150 -0
- package/types/ngwr-lightbox-testing.d.ts +154 -0
- package/types/ngwr-lightbox.d.ts +6 -0
- package/types/ngwr-line-chart-testing.d.ts +93 -0
- package/types/ngwr-marquee-testing.d.ts +280 -0
- package/types/ngwr-meter-group-testing.d.ts +67 -0
- package/types/ngwr-overlay.d.ts +19 -1
- package/types/ngwr-rotating-text-testing.d.ts +191 -0
- package/types/ngwr-select.d.ts +32 -0
- package/types/ngwr-shiny-text-testing.d.ts +165 -0
- package/types/ngwr-slider.d.ts +15 -0
- package/types/ngwr-sparkline-testing.d.ts +69 -0
- package/types/ngwr-speed-dial-testing.d.ts +195 -0
- package/types/ngwr-splash-cursor-testing.d.ts +95 -0
- package/types/ngwr-split-text-testing.d.ts +209 -0
- package/types/ngwr-split-text.d.ts +12 -0
- package/types/ngwr-splitter-testing.d.ts +174 -0
- package/types/ngwr-spotlight-card-testing.d.ts +303 -0
- package/types/ngwr-star-border-testing.d.ts +135 -0
- package/types/ngwr-table.d.ts +10 -1
- package/types/ngwr-tilt-card-testing.d.ts +158 -0
- package/types/ngwr-tilt-card.d.ts +2 -0
- package/types/ngwr-toast.d.ts +14 -5
- package/types/ngwr-tour-testing.d.ts +143 -0
- package/types/ngwr-tree.d.ts +7 -0
- package/types/ngwr-typewriter-testing.d.ts +201 -0
- package/types/ngwr-version.d.ts +1 -1
- package/types/ngwr-waves-testing.d.ts +121 -0
- package/types/ngwr-window-testing.d.ts +263 -0
- package/types/ngwr-window.d.ts +27 -10
- package/window/styles/_index.scss +44 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngwr-date-picker-testing.mjs","sources":["../../../projects/lib/date-picker/testing/wr-date-picker-day-harness.ts","../../../projects/lib/date-picker/testing/wr-time-panel-harness.ts","../../../projects/lib/date-picker/testing/wr-date-picker-harness-base.ts","../../../projects/lib/date-picker/testing/wr-date-picker-harness.ts","../../../projects/lib/date-picker/testing/wr-date-range-picker-harness.ts","../../../projects/lib/date-picker/testing/ngwr-date-picker-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';\n\nimport type { WrDatePickerDayHarnessFilters } from './interfaces';\n\n/**\n * Test harness for one day cell of the calendar inside a date-picker popup.\n *\n * Reached through `WrDatePickerHarness.getDay()` / `getDays()` (or the range\n * picker's), which scope it to ONE picker's panel — the cells are in the overlay\n * container, so a bare query would answer with whichever picker opened first.\n *\n * State is read the way a screen reader reads it (`role`, `aria-selected`,\n * `aria-disabled`) with the `.wr-calendar__day--*` modifiers filling in what\n * ARIA has no word for: today, the spill days of the neighbouring months, and\n * the interior of a picked range.\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDatePickerDayHarness extends ComponentHarness {\n static hostSelector = '.wr-calendar__day';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrDatePickerDayHarnessFilters = {}): HarnessPredicate<WrDatePickerDayHarness> {\n return new HarnessPredicate(WrDatePickerDayHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getText(), text))\n .addOption('selected', options.selected, async (harness, selected) => (await harness.isSelected()) === selected)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('inRange', options.inRange, async (harness, inRange) => (await harness.isInRange()) === inRange);\n }\n\n /** The day number as rendered. */\n async getText(): Promise<string> {\n return (await this.host()).text();\n }\n\n /** The day number as a number, for mapping a list of cells to dates. */\n async getDayOfMonth(): Promise<number> {\n return Number(await this.getText());\n }\n\n /** `'gridcell'` — the cell is a button, and the grid semantics are what a spec should assert. */\n async getRole(): Promise<string | null> {\n return (await this.host()).getAttribute('role');\n }\n\n /** Whether this day is (one end of) the current selection. */\n async isSelected(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-selected')) === 'true';\n }\n\n /**\n * Whether the day refuses selection — `min` / `max` or `dateFilter` ruled it\n * out, or the whole calendar is disabled.\n */\n async isDisabled(): Promise<boolean> {\n return (await (await this.host()).getAttribute('aria-disabled')) === 'true';\n }\n\n /** Whether the cell is today's date. */\n async isToday(): Promise<boolean> {\n return (await this.host()).hasClass('wr-calendar__day--today');\n }\n\n /**\n * Whether the cell belongs to a neighbouring month. The grid is always 6x7, so\n * every month is padded with days that are selectable but not \"in\" the month —\n * which is why {@link WrDatePickerHarness.getDay} skips them.\n */\n async isOutOfMonth(): Promise<boolean> {\n return (await this.host()).hasClass('wr-calendar__day--out-of-month');\n }\n\n /** Whether the day lies strictly between the two ends of a picked range. */\n async isInRange(): Promise<boolean> {\n return (await this.host()).hasClass('wr-calendar__day--in-range');\n }\n\n /**\n * Whether this is the roving cell — the grid's single tab stop, and where the\n * arrow keys move from.\n *\n * At most ONE cell answers `true`, and it is not always one: the calendar's\n * roving date does not follow `next()` / `previous()`, so a grid navigated away\n * from that month has no active cell (and, deliberately or not, no tab stop)\n * until a cell is clicked or the arrows move it.\n */\n async isActive(): Promise<boolean> {\n return (await this.host()).hasClass('wr-calendar__day--focused');\n }\n\n /** Click the cell. A disabled cell swallows the click — assert, do not assume. */\n async click(): Promise<void> {\n return (await this.host()).click();\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerTimeFields } from './interfaces';\n\n/** Column order inside the stepper, which is also the order the boxes render in. */\nconst UNITS = ['hours', 'minutes', 'seconds'] as const;\n\n/** One unit of a time stepper. */\ntype Unit = (typeof UNITS)[number];\n\nfunction missingColumn(unit: Unit, method: string): string {\n return `${method}(): this stepper has no ${unit} column — seconds only render with [showSeconds]=\"true\".`;\n}\n\n/**\n * The `<wr-time-picker>` stepper inside a date-picker popup.\n *\n * Deliberately NOT exported from the entry point: the component is `@internal`\n * (`<wr-date-picker mode=\"time\">` is how a consumer gets one), so the picker\n * harnesses re-publish what it can do — `getTime()`, `setTime()`, `stepTime()`,\n * `toggleMeridiem()` — rather than handing out this class. It exists so the\n * range picker, which renders one stepper PER END, can address them\n * independently instead of by CSS nth-of-type gymnastics.\n *\n * Units are addressed by position, not by the boxes' `aria-label`: every string\n * in this panel is still hard-coded English (the box labels, the steppers'\n * \"Increment hours\", the AM/PM text), so keying on them would make this harness\n * the thing that breaks when they are moved into the i18n catalog. Column order\n * is the layout contract.\n *\n * @internal\n */\nexport class WrTimePanelHarness extends ComponentHarness {\n static hostSelector = '.wr-time-picker';\n\n /** The stepper's value as `HH:MM`, plus `:SS` and ` AM`/` PM` when those columns exist. */\n async getTimeText(): Promise<string> {\n const parts = [await this.getValue('hours'), await this.getValue('minutes')];\n const seconds = await this.getOptionalValue('seconds');\n if (seconds !== null) parts.push(seconds);\n\n const meridiem = await this.getMeridiem();\n return meridiem ? `${parts.join(':')} ${meridiem}` : parts.join(':');\n }\n\n /** `'AM'` / `'PM'`, or `null` when the stepper is in 24-hour mode. */\n async getMeridiem(): Promise<string | null> {\n const label = await this.locatorForOptional('.wr-time-picker__label')();\n return label ? label.text() : null;\n }\n\n /**\n * Type into the boxes named by `fields`, leaving the rest alone.\n *\n * Each box is cleared and retyped rather than assigned: the panel reads an\n * emptied box as someone mid-retype and deliberately commits nothing for it, so\n * a harness that only cleared would look like a no-op.\n */\n async setFields(fields: WrDatePickerTimeFields): Promise<void> {\n for (const unit of UNITS) {\n const value = fields[unit];\n if (value === undefined) continue;\n\n const box = await this.box(unit, 'setTime');\n await box.clear();\n await box.sendKeys(String(value));\n }\n }\n\n /** Click a unit's ▲ / ▼ button once. Minutes and seconds move by the picker's `step`. */\n async step(unit: Unit, direction: 1 | -1): Promise<void> {\n // The AM/PM column contributes a ▲ and a ▼ of its own, and it is always\n // last — so it cannot be excluded by index alone: with seconds off, the\n // third ▲ in the panel IS the meridiem toggle.\n const suffix = direction === 1 ? 'up' : 'down';\n const buttons = await this.locatorForAll(\n `.wr-time-picker__col:not(.wr-time-picker__col--ampm) .wr-time-picker__step--${suffix}`\n )();\n\n const button = buttons[UNITS.indexOf(unit)];\n if (!button) throw new Error(missingColumn(unit, 'stepTime'));\n\n await button.click();\n }\n\n /** Flip AM ↔ PM. Throws on a 24-hour stepper, which has no such control. */\n async toggleMeridiem(): Promise<void> {\n const button = await this.locatorForOptional('.wr-time-picker__col--ampm .wr-time-picker__step--up')();\n if (!button) {\n throw new Error(\n 'toggleMeridiem(): this stepper has no AM / PM control — it is in 24-hour mode. Pass ' +\n 'timeFormat=\"12h\" (or a locale that resolves to 12-hour time) to get one.'\n );\n }\n\n await button.click();\n }\n\n /** A unit's rendered (zero-padded) value. */\n private async getValue(unit: Unit): Promise<string> {\n return (await this.box(unit, 'getTime')).getProperty<string>('value');\n }\n\n /** Same, but `null` when the column is not rendered at all. */\n private async getOptionalValue(unit: Unit): Promise<string | null> {\n const boxes = await this.boxes();\n const box = boxes[UNITS.indexOf(unit)];\n return box ? box.getProperty<string>('value') : null;\n }\n\n private async box(unit: Unit, method: string): Promise<TestElement> {\n const boxes = await this.boxes();\n const box = boxes[UNITS.indexOf(unit)];\n if (!box) throw new Error(missingColumn(unit, method));\n return box;\n }\n\n /** The typable boxes, in column order — the AM/PM column is a `<span>`, not one of these. */\n private async boxes(): Promise<TestElement[]> {\n return this.locatorForAll('.wr-time-picker__input')();\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, type HarnessLoader, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerDayHarnessFilters } from './interfaces';\nimport { WrDatePickerDayHarness } from './wr-date-picker-day-harness';\nimport { WrTimePanelHarness } from './wr-time-panel-harness';\n\n/** The first element whose trimmed text is exactly `text`. */\nasync function byText(elements: TestElement[], text: string): Promise<TestElement | null> {\n for (const element of elements) {\n if ((await element.text()) === text) return element;\n }\n return null;\n}\n\n/**\n * Everything `<wr-date-picker>` and `<wr-date-range-picker>` do identically: the\n * trigger button, the popup it owns, and the `<wr-calendar>` inside it. Both\n * components render the SAME trigger element (`.wr-date-picker__trigger` — the\n * range picker reuses the class rather than minting its own) and both publish\n * their popup's id through the trigger's `aria-controls`, so the scoping story is\n * shared too.\n *\n * Not exported from the entry point — a consumer works with one of the two\n * concrete harnesses.\n *\n * @internal\n */\nexport abstract class WrDatePickerHarnessBase extends ComponentHarness {\n /** Concrete class name, so errors read like the harness the caller reached for. */\n protected abstract readonly harnessName: string;\n\n /**\n * Whether the popup is up.\n *\n * Read from the trigger's `aria-expanded` rather than from a host class: unlike\n * `wr-select`, neither picker carries an `--open` modifier, and the ARIA state\n * is the thing a consumer's users actually get told.\n */\n async isOpen(): Promise<boolean> {\n return (await (await this.trigger()).getAttribute('aria-expanded')) === 'true';\n }\n\n /** Open the popup from the trigger. An already-open picker is left alone. */\n async open(): Promise<void> {\n if (await this.isOpen()) return;\n\n await (await this.trigger()).click();\n if (await this.isOpen()) return;\n\n throw new Error(\n `${this.harnessName}.open(): the trigger did not open a popup. A disabled picker refuses to open, and so ` +\n 'does a readonly <wr-date-range-picker> — a readonly <wr-date-picker>, deliberately, still opens.'\n );\n }\n\n /**\n * Close the popup by clicking the trigger again. A closed picker is left alone.\n *\n * Deliberately not Escape: the CDK dispatches a keydown to the TOP-MOST overlay,\n * so with two pickers open this would close whichever opened last rather than\n * this one. The trigger only ever toggles its own popup.\n */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n\n await (await this.trigger()).click();\n }\n\n /** The popup's role — `'dialog'`, which is what the trigger's `aria-haspopup` promises. */\n async getPanelRole(): Promise<string | null> {\n return (await this.panel()).getAttribute('role');\n }\n\n /**\n * The popup's accessible name. Defaults to a catalog string chosen by `mode`\n * (\"Choose date\", \"Choose time\", …) and is overridden by `[panelAriaLabel]`.\n */\n async getPanelAriaLabel(): Promise<string | null> {\n return (await this.panel()).getAttribute('aria-label');\n }\n\n /**\n * Which of the calendar's three views is showing. The header label zooms out\n * (day → month → year); picking a month or a year zooms back in.\n */\n async getView(): Promise<'day' | 'month' | 'year'> {\n if (await this.inPanel('[role=\"grid\"]')) return 'day';\n if (await this.inPanel('.wr-calendar__months')) return 'month';\n if (await this.inPanel('.wr-calendar__years')) return 'year';\n\n throw new Error(this.noCalendar('getView'));\n }\n\n /**\n * The calendar's header: `'January 2025'` in the day view, `'2025'` in the month\n * view, `'2016 – 2027'` in the year view.\n */\n async getPanelHeader(): Promise<string> {\n const label = await this.inPanel('.wr-calendar__label');\n if (!label) throw new Error(this.noCalendar('getPanelHeader'));\n\n return label.text();\n }\n\n /** The weekday strip, in display order — `role=\"columnheader\"` cells of the grid's first row. */\n async getWeekdayLabels(): Promise<string[]> {\n const cells = await this.allInPanel('[role=\"columnheader\"]');\n if (cells.length === 0) {\n throw new Error(\n `${this.harnessName}.getWeekdayLabels(): no weekday strip. Only the day view has one, and mode=\"time\" ` +\n 'has no calendar at all.'\n );\n }\n\n return Promise.all(cells.map(cell => cell.text()));\n }\n\n /**\n * Step the calendar forwards. What one step MEANS follows the active view, as\n * the button's own `aria-label` says: a month in the day view, a year in the\n * month view, twelve years in the year view.\n */\n async next(): Promise<void> {\n await this.navigate('next');\n }\n\n /** Step the calendar backwards — see {@link next} for what one step covers. */\n async previous(): Promise<void> {\n await this.navigate('prev');\n }\n\n /**\n * Zoom out one level by clicking the header, the way a user does: day → month,\n * month → year. Throws at the year view, where the header is inert.\n */\n async zoomOut(): Promise<void> {\n const label = await this.inPanel('.wr-calendar__label');\n if (!label) throw new Error(this.noCalendar('zoomOut'));\n\n if (await label.getProperty<boolean>('disabled')) {\n throw new Error(\n `${this.harnessName}.zoomOut(): the header is inert — the calendar is already showing the year view ` +\n '(or the whole picker is disabled), and there is nothing further to zoom out to.'\n );\n }\n\n await label.click();\n }\n\n /** Pick a month in the month view, by 0-based index or by the label the calendar renders. */\n async selectMonth(month: number | string): Promise<void> {\n const chips = await this.allInPanel('.wr-calendar__months .wr-calendar__chip');\n if (chips.length === 0) {\n throw new Error(\n `${this.harnessName}.selectMonth(): the calendar is not showing its month view — call zoomOut() first.`\n );\n }\n\n const chip = typeof month === 'number' ? chips[month] : await byText(chips, month);\n if (!chip) {\n throw new Error(\n `${this.harnessName}.selectMonth(${JSON.stringify(month)}): no such month. Pass a 0-based index, or the ` +\n 'label the calendar renders — a short month name in the active locale.'\n );\n }\n\n await chip.click();\n }\n\n /** Pick a year in the year view. Only the twelve on show can be picked. */\n async selectYear(year: number): Promise<void> {\n const chips = await this.allInPanel('.wr-calendar__years .wr-calendar__chip');\n if (chips.length === 0) {\n throw new Error(\n `${this.harnessName}.selectYear(): the calendar is not showing its year view — call zoomOut() until ` +\n 'getView() answers \"year\".'\n );\n }\n\n const chip = await byText(chips, String(year));\n if (!chip) {\n const shown = await Promise.all(chips.map(candidate => candidate.text()));\n throw new Error(\n `${this.harnessName}.selectYear(${year}): the year view is showing ${shown[0]}-${shown[shown.length - 1]} ` +\n '— call previous() / next() to move the twelve-year window first.'\n );\n }\n\n await chip.click();\n }\n\n /**\n * The day cells of the calendar in this picker's popup, in DOM order — 42 of\n * them, since the grid is always six weeks and pads into the neighbouring\n * months.\n */\n async getDays(filters: WrDatePickerDayHarnessFilters = {}): Promise<WrDatePickerDayHarness[]> {\n await this.requireGrid('getDays');\n\n return (await this.panelLoader()).getAllHarnesses(WrDatePickerDayHarness.with(filters));\n }\n\n /**\n * One day of the month on show. The padding days of the neighbouring months are\n * skipped, so `getDay(1)` is the 1st of THIS month and never the 1st of the next.\n */\n async getDay(dayOfMonth: number): Promise<WrDatePickerDayHarness> {\n const cells = await this.getDays({ text: String(dayOfMonth) });\n for (const cell of cells) {\n if (!(await cell.isOutOfMonth())) return cell;\n }\n\n throw new Error(\n `${this.harnessName}.getDay(${dayOfMonth}): the month on show (${await this.getPanelHeader()}) has no such ` +\n 'day. Cells belonging to the neighbouring months are deliberately not matched — call previous() / next() ' +\n 'to change month.'\n );\n }\n\n /** Click a day of the month on show. */\n async selectDay(dayOfMonth: number): Promise<void> {\n const cell = await this.getDay(dayOfMonth);\n if (await cell.isDisabled()) {\n throw new Error(\n `${this.harnessName}.selectDay(${dayOfMonth}): that day is disabled — min / max or dateFilter rules it ` +\n 'out, and a disabled cell swallows the click rather than picking anything.'\n );\n }\n\n await cell.click();\n }\n\n /** The trigger button. Both components render it with the same class. */\n protected async trigger(): Promise<TestElement> {\n return this.locatorFor('.wr-date-picker__trigger')();\n }\n\n /**\n * The id of THIS picker's popup.\n *\n * The trigger publishes it as `aria-controls` only while the popup is up, which\n * makes one lookup answer both questions — is it open, and which of the panes\n * in the shared overlay container is ours.\n */\n protected async panelId(): Promise<string> {\n const id = await (await this.trigger()).getAttribute('aria-controls');\n if (!id) {\n throw new Error(\n `${this.harnessName}: the popup is closed — call open() first. The trigger only publishes aria-controls ` +\n 'while its popup is up.'\n );\n }\n\n return id;\n }\n\n /** A loader scoped to THIS picker's popup, so a second open picker cannot answer for it. */\n protected async panelLoader(): Promise<HarnessLoader> {\n return this.documentRootLocatorFactory().harnessLoaderFor(`#${await this.panelId()}`);\n }\n\n /** One element inside THIS picker's popup, or `null`. */\n protected async inPanel(selector: string): Promise<TestElement | null> {\n return this.documentRootLocatorFactory().locatorForOptional(`#${await this.panelId()} ${selector}`)();\n }\n\n /** Every matching element inside THIS picker's popup, in DOM order. */\n protected async allInPanel(selector: string): Promise<TestElement[]> {\n return this.documentRootLocatorFactory().locatorForAll(`#${await this.panelId()} ${selector}`)();\n }\n\n /**\n * The time steppers in this popup, in DOM order — none in `date` mode, one for\n * `time` / `datetime`, and two (start then end) for a datetime range.\n */\n protected async timePanels(): Promise<WrTimePanelHarness[]> {\n return (await this.panelLoader()).getAllHarnesses(WrTimePanelHarness);\n }\n\n private async navigate(direction: 'prev' | 'next'): Promise<void> {\n const button = await this.inPanel(`.wr-calendar__nav--${direction}`);\n if (!button) throw new Error(this.noCalendar(direction === 'next' ? 'next' : 'previous'));\n\n await button.click();\n }\n\n /** Fail early on the day-cell methods rather than answering with an empty list. */\n private async requireGrid(method: string): Promise<void> {\n if (await this.inPanel('[role=\"grid\"]')) return;\n\n throw new Error(\n `${this.harnessName}.${method}(): this popup has no day grid. mode=\"time\" renders a time stepper only, and a ` +\n 'calendar zoomed out to its month / year view offers chips instead of day cells — selectMonth() / ' +\n 'selectYear() come back to the days.'\n );\n }\n\n private async panel(): Promise<TestElement> {\n return this.documentRootLocatorFactory().locatorFor(`#${await this.panelId()}`)();\n }\n\n private noCalendar(method: string): string {\n return `${this.harnessName}.${method}(): this popup has no calendar — mode=\"time\" renders a time stepper only.`;\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerHarnessFilters, WrDatePickerTimeFields } from './interfaces';\nimport { WrDatePickerHarnessBase } from './wr-date-picker-harness-base';\nimport type { WrTimePanelHarness } from './wr-time-panel-harness';\n\n/**\n * Test harness for `<wr-date-picker>` — the `date`, `time` and `datetime` modes\n * of the one component.\n *\n * The popup is NOT inside the picker: it is a pane in the overlay container, a\n * sibling of the whole app. Everything inside it is therefore reached through the\n * document root, scoped by the id the trigger publishes as `aria-controls` —\n * which is what keeps two pickers on one page from reading each other's calendar.\n *\n * The value lives in a text field, so there are two ways in and the harness\n * offers both: {@link setValueText} types a date, {@link selectDay} picks one out\n * of the grid. There is deliberately no clear BUTTON on this component — an\n * emptied field is what clears the value, and that is what {@link clear} does.\n *\n * @example\n * ```ts\n * const picker = await loader.getHarness(WrDatePickerHarness);\n *\n * await picker.open();\n * expect(await picker.getPanelHeader()).toBe('January 2025');\n * await picker.selectDay(14);\n *\n * expect(await picker.getValueText()).toBe('14.01.2025');\n * expect(await (await picker.getDay(14)).isSelected()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDatePickerHarness extends WrDatePickerHarnessBase {\n static hostSelector = 'wr-date-picker';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrDatePickerHarnessFilters = {}): HarnessPredicate<WrDatePickerHarness> {\n return new HarnessPredicate(WrDatePickerHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getValueText(), text))\n .addOption('placeholder', options.placeholder, (harness, placeholder) =>\n HarnessPredicate.stringMatches(harness.getPlaceholder(), placeholder)\n )\n .addOption('mode', options.mode, async (harness, mode) => (await harness.getMode()) === mode)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n protected readonly harnessName = 'WrDatePickerHarness';\n\n /** Which mode the picker is in, read from the `wr-date-picker--*` modifier. */\n async getMode(): Promise<'date' | 'time' | 'datetime'> {\n const host = await this.host();\n if (await host.hasClass('wr-date-picker--time')) return 'time';\n if (await host.hasClass('wr-date-picker--datetime')) return 'datetime';\n return 'date';\n }\n\n /**\n * What the field is showing: the value in the picker's `format`, or `''` when it\n * is empty and the placeholder is up.\n */\n async getValueText(): Promise<string> {\n return (await this.field()).getProperty<string>('value');\n }\n\n /** The field's placeholder. */\n async getPlaceholder(): Promise<string> {\n return (await this.field()).getProperty<string>('placeholder');\n }\n\n /**\n * Type a date into the field, replacing whatever is there.\n *\n * The field is cleared first, so the value passes through `null` on the way —\n * exactly as it does for a user who selects all and types over it. The picker\n * parses on every keystroke and commits only when the text parses in full, so a\n * partial or out-of-bounds entry leaves the model on its last good value.\n */\n async setValueText(text: string): Promise<void> {\n const field = await this.field();\n await field.clear();\n // `sendKeys()` with an empty string throws — clearing was the whole request.\n if (text) await field.sendKeys(text);\n }\n\n /**\n * Empty the field, which clears the value.\n *\n * There is no clear button to click: unlike `wr-select`, this component ships no\n * `×` affordance, and an empty field is the documented way to get back to `null`.\n */\n async clear(): Promise<void> {\n await (await this.field()).clear();\n }\n\n /** Whether the picker refuses interaction. */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-date-picker--disabled');\n }\n\n /**\n * Whether the field refuses typing. A read-only picker still OPENS its popup —\n * documented behaviour, and the one place it parts company with the range picker.\n */\n async isReadonly(): Promise<boolean> {\n return (await this.field()).getProperty<boolean>('readOnly');\n }\n\n /** Move focus to the field. */\n async focus(): Promise<void> {\n return (await this.field()).focus();\n }\n\n /**\n * Blur the field, which is what reformats a value to canonical and emits `touch`\n * so a bound `[formField]` can mark itself touched.\n */\n async blur(): Promise<void> {\n return (await this.field()).blur();\n }\n\n /** Whether the field currently has focus. */\n async isFocused(): Promise<boolean> {\n return (await this.field()).isFocused();\n }\n\n /**\n * The open time stepper's value as `HH:MM`, plus `:SS` when `showSeconds` is on\n * and ` AM`/` PM` in 12-hour mode.\n */\n async getTime(): Promise<string> {\n return (await this.timePanel()).getTimeText();\n }\n\n /** Type into the open time stepper's boxes. Omitted fields are left alone. */\n async setTime(fields: WrDatePickerTimeFields): Promise<void> {\n await (await this.timePanel()).setFields(fields);\n }\n\n /** Click one of the stepper's ▲ / ▼ buttons. Minutes and seconds move by `step`. */\n async stepTime(unit: 'hours' | 'minutes' | 'seconds', direction: 1 | -1): Promise<void> {\n await (await this.timePanel()).step(unit, direction);\n }\n\n /** Flip the stepper between AM and PM. Throws in 24-hour mode, which has no such control. */\n async toggleMeridiem(): Promise<void> {\n await (await this.timePanel()).toggleMeridiem();\n }\n\n private async field(): Promise<TestElement> {\n return this.locatorFor('input[wrInput]')();\n }\n\n /** The one stepper in the popup — `panelId()` has already refused a closed picker. */\n private async timePanel(): Promise<WrTimePanelHarness> {\n const [panel] = await this.timePanels();\n if (!panel) {\n throw new Error(\n `${this.harnessName}: this popup has no time stepper — mode=\"date\" renders a calendar only. Use mode=\"time\" ` +\n 'or mode=\"datetime\".'\n );\n }\n\n return panel;\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerTimeFields, WrDateRangePickerHarnessFilters } from './interfaces';\nimport { WrDatePickerHarnessBase } from './wr-date-picker-harness-base';\nimport type { WrTimePanelHarness } from './wr-time-panel-harness';\n\n/** Which end of the range a call applies to. */\ntype End = 'start' | 'end';\n\n/**\n * Test harness for `<wr-date-range-picker>` — two text fields over one range\n * calendar.\n *\n * A separate harness rather than a mode of {@link WrDatePickerHarness}, because\n * the DOM a spec has to reach is genuinely different: two fields with a separator\n * between them, and in `datetime` mode two time steppers instead of one. Every\n * call that could mean either field takes an explicit `'start'` / `'end'` — the\n * component's own bugs were nearly all \"which end did that belong to\", and a\n * harness that guessed would reproduce them.\n *\n * The popup, the calendar and the scoping work exactly as they do for the single\n * picker: a pane in the overlay container, addressed by the id the trigger\n * publishes as `aria-controls`.\n *\n * @example\n * ```ts\n * const period = await loader.getHarness(WrDateRangePickerHarness);\n *\n * await period.open();\n * await period.selectDay(14);\n * await period.selectDay(20);\n *\n * expect([await period.getStartText(), await period.getEndText()]).toEqual(['14.01.2025', '20.01.2025']);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDateRangePickerHarness extends WrDatePickerHarnessBase {\n static hostSelector = 'wr-date-range-picker';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrDateRangePickerHarnessFilters = {}): HarnessPredicate<WrDateRangePickerHarness> {\n return new HarnessPredicate(WrDateRangePickerHarness, options)\n .addOption('startText', options.startText, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getStartText(), text)\n )\n .addOption('endText', options.endText, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getEndText(), text)\n )\n .addOption('mode', options.mode, async (harness, mode) => (await harness.getMode()) === mode)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n protected readonly harnessName = 'WrDateRangePickerHarness';\n\n /** Which mode the picker is in, read from the `wr-date-range-picker--*` modifier. */\n async getMode(): Promise<'date' | 'datetime'> {\n return (await (await this.host()).hasClass('wr-date-range-picker--datetime')) ? 'datetime' : 'date';\n }\n\n /** What the start field is showing — `''` while that end is unpicked. */\n async getStartText(): Promise<string> {\n return (await this.field('start')).getProperty<string>('value');\n }\n\n /** What the end field is showing — `''` while that end is unpicked. */\n async getEndText(): Promise<string> {\n return (await this.field('end')).getProperty<string>('value');\n }\n\n /** The glyph rendered between the two fields (`[separator]`, `'–'` by default). */\n async getSeparator(): Promise<string> {\n return (await this.locatorFor('.wr-date-range-picker__separator')()).text();\n }\n\n /** The placeholder of one field. */\n async getPlaceholder(end: End): Promise<string> {\n return (await this.field(end)).getProperty<string>('placeholder');\n }\n\n /**\n * Type a date into the start field, replacing whatever is there.\n *\n * Ends typed out of order are NOT swapped here — the component settles the\n * ordering when the interaction is over, so call {@link blur} (or close the\n * popup) to see a normalised range.\n */\n async setStartText(text: string): Promise<void> {\n await this.type('start', text);\n }\n\n /** Type a date into the end field — see {@link setStartText} on ordering. */\n async setEndText(text: string): Promise<void> {\n await this.type('end', text);\n }\n\n /** Empty both fields, which clears the range. Neither end has a clear button. */\n async clear(): Promise<void> {\n await (await this.field('start')).clear();\n await (await this.field('end')).clear();\n }\n\n /** Whether the picker refuses interaction. */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-date-range-picker--disabled');\n }\n\n /**\n * Whether the fields refuse typing. Stricter than `<wr-date-picker>`: a\n * read-only range picker also refuses to open, since two fields over one\n * calendar leaves no reading of \"untypeable\" that still lets the grid rewrite\n * both ends.\n */\n async isReadonly(): Promise<boolean> {\n return (await this.field('start')).getProperty<boolean>('readOnly');\n }\n\n /** Move focus to one field. */\n async focus(end: End): Promise<void> {\n return (await this.field(end)).focus();\n }\n\n /**\n * Blur one field. This is the moment an out-of-order range is put back in\n * order, and — when focus leaves the control rather than hopping to the other\n * end — the moment `touch` is emitted.\n */\n async blur(end: End): Promise<void> {\n return (await this.field(end)).blur();\n }\n\n /** Whether one field currently has focus. */\n async isFocused(end: End): Promise<boolean> {\n return (await this.field(end)).isFocused();\n }\n\n /**\n * One end's time stepper as `HH:MM`, plus `:SS` when `showSeconds` is on and\n * ` AM`/` PM` in 12-hour mode. `datetime` mode only.\n */\n async getTime(end: End): Promise<string> {\n return (await this.timePanel(end)).getTimeText();\n }\n\n /** Type into one end's time stepper. Omitted fields are left alone. */\n async setTime(end: End, fields: WrDatePickerTimeFields): Promise<void> {\n await (await this.timePanel(end)).setFields(fields);\n }\n\n /** Click one of an end's ▲ / ▼ buttons. Minutes and seconds move by `step`. */\n async stepTime(end: End, unit: 'hours' | 'minutes' | 'seconds', direction: 1 | -1): Promise<void> {\n await (await this.timePanel(end)).step(unit, direction);\n }\n\n /** Flip one end between AM and PM. Throws in 24-hour mode, which has no such control. */\n async toggleMeridiem(end: End): Promise<void> {\n await (await this.timePanel(end)).toggleMeridiem();\n }\n\n private async type(end: End, text: string): Promise<void> {\n const field = await this.field(end);\n await field.clear();\n // `sendKeys()` with an empty string throws — clearing was the whole request.\n if (text) await field.sendKeys(text);\n }\n\n /** The two fields are addressed by position, which is the order they render in. */\n private async field(end: End): Promise<TestElement> {\n const fields = await this.locatorForAll('.wr-date-range-picker__input')();\n const field = fields[end === 'start' ? 0 : 1];\n if (!field) {\n throw new Error(`${this.harnessName}: no \"${end}\" field — the picker rendered ${fields.length} of them.`);\n }\n\n return field;\n }\n\n /**\n * One end's stepper. Both are mounted together in `datetime` mode, start first —\n * the group labels are localized, so position is the addressable thing.\n */\n private async timePanel(end: End): Promise<WrTimePanelHarness> {\n const panels = await this.timePanels();\n if (panels.length === 0) {\n throw new Error(\n `${this.harnessName}: this popup has no time steppers — mode=\"date\" renders a calendar only. Use ` +\n 'mode=\"datetime\".'\n );\n }\n\n const panel = panels[end === 'start' ? 0 : 1];\n if (!panel) {\n throw new Error(`${this.harnessName}: no \"${end}\" time stepper — the popup rendered ${panels.length}.`);\n }\n\n return panel;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngwr-date-picker-testing.mjs","sources":["../../../projects/lib/date-picker/testing/wr-date-picker-day-harness.ts","../../../projects/lib/date-picker/testing/wr-time-panel-harness.ts","../../../projects/lib/date-picker/testing/wr-date-picker-harness-base.ts","../../../projects/lib/date-picker/testing/wr-date-picker-harness.ts","../../../projects/lib/date-picker/testing/wr-date-range-picker-harness.ts","../../../projects/lib/date-picker/testing/ngwr-date-picker-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\n/**\n * The day cell of a date-picker's calendar, under the name it shipped as.\n *\n * There is one implementation, and it lives with the component that owns the\n * element: a picker's popup IS a `<wr-calendar>`, and `.wr-calendar__day` is its\n * cell. `ngwr/calendar/testing` is therefore the home, and this alias keeps\n * `WrDatePickerDayHarness` working for every spec that already imports it — the two\n * names are the same class, so a harness from either query is usable with either.\n */\nexport { WrCalendarDayHarness as WrDatePickerDayHarness } from 'ngwr/calendar/testing';\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerTimeFields } from './interfaces';\n\n/** Column order inside the stepper, which is also the order the boxes render in. */\nconst UNITS = ['hours', 'minutes', 'seconds'] as const;\n\n/** One unit of a time stepper. */\ntype Unit = (typeof UNITS)[number];\n\nfunction missingColumn(unit: Unit, method: string): string {\n return `${method}(): this stepper has no ${unit} column — seconds only render with [showSeconds]=\"true\".`;\n}\n\n/**\n * The `<wr-time-picker>` stepper inside a date-picker popup.\n *\n * Deliberately NOT exported from the entry point: the component is `@internal`\n * (`<wr-date-picker mode=\"time\">` is how a consumer gets one), so the picker\n * harnesses re-publish what it can do — `getTime()`, `setTime()`, `stepTime()`,\n * `toggleMeridiem()` — rather than handing out this class. It exists so the\n * range picker, which renders one stepper PER END, can address them\n * independently instead of by CSS nth-of-type gymnastics.\n *\n * Units are addressed by position, not by the boxes' `aria-label`: every string\n * in this panel is still hard-coded English (the box labels, the steppers'\n * \"Increment hours\", the AM/PM text), so keying on them would make this harness\n * the thing that breaks when they are moved into the i18n catalog. Column order\n * is the layout contract.\n *\n * @internal\n */\nexport class WrTimePanelHarness extends ComponentHarness {\n static hostSelector = '.wr-time-picker';\n\n /** The stepper's value as `HH:MM`, plus `:SS` and ` AM`/` PM` when those columns exist. */\n async getTimeText(): Promise<string> {\n const parts = [await this.getValue('hours'), await this.getValue('minutes')];\n const seconds = await this.getOptionalValue('seconds');\n if (seconds !== null) parts.push(seconds);\n\n const meridiem = await this.getMeridiem();\n return meridiem ? `${parts.join(':')} ${meridiem}` : parts.join(':');\n }\n\n /** `'AM'` / `'PM'`, or `null` when the stepper is in 24-hour mode. */\n async getMeridiem(): Promise<string | null> {\n const label = await this.locatorForOptional('.wr-time-picker__label')();\n return label ? label.text() : null;\n }\n\n /**\n * Type into the boxes named by `fields`, leaving the rest alone.\n *\n * Each box is cleared and retyped rather than assigned: the panel reads an\n * emptied box as someone mid-retype and deliberately commits nothing for it, so\n * a harness that only cleared would look like a no-op.\n */\n async setFields(fields: WrDatePickerTimeFields): Promise<void> {\n for (const unit of UNITS) {\n const value = fields[unit];\n if (value === undefined) continue;\n\n const box = await this.box(unit, 'setTime');\n await box.clear();\n await box.sendKeys(String(value));\n }\n }\n\n /** Click a unit's ▲ / ▼ button once. Minutes and seconds move by the picker's `step`. */\n async step(unit: Unit, direction: 1 | -1): Promise<void> {\n // The AM/PM column contributes a ▲ and a ▼ of its own, and it is always\n // last — so it cannot be excluded by index alone: with seconds off, the\n // third ▲ in the panel IS the meridiem toggle.\n const suffix = direction === 1 ? 'up' : 'down';\n const buttons = await this.locatorForAll(\n `.wr-time-picker__col:not(.wr-time-picker__col--ampm) .wr-time-picker__step--${suffix}`\n )();\n\n const button = buttons[UNITS.indexOf(unit)];\n if (!button) throw new Error(missingColumn(unit, 'stepTime'));\n\n await button.click();\n }\n\n /** Flip AM ↔ PM. Throws on a 24-hour stepper, which has no such control. */\n async toggleMeridiem(): Promise<void> {\n const button = await this.locatorForOptional('.wr-time-picker__col--ampm .wr-time-picker__step--up')();\n if (!button) {\n throw new Error(\n 'toggleMeridiem(): this stepper has no AM / PM control — it is in 24-hour mode. Pass ' +\n 'timeFormat=\"12h\" (or a locale that resolves to 12-hour time) to get one.'\n );\n }\n\n await button.click();\n }\n\n /** A unit's rendered (zero-padded) value. */\n private async getValue(unit: Unit): Promise<string> {\n return (await this.box(unit, 'getTime')).getProperty<string>('value');\n }\n\n /** Same, but `null` when the column is not rendered at all. */\n private async getOptionalValue(unit: Unit): Promise<string | null> {\n const boxes = await this.boxes();\n const box = boxes[UNITS.indexOf(unit)];\n return box ? box.getProperty<string>('value') : null;\n }\n\n private async box(unit: Unit, method: string): Promise<TestElement> {\n const boxes = await this.boxes();\n const box = boxes[UNITS.indexOf(unit)];\n if (!box) throw new Error(missingColumn(unit, method));\n return box;\n }\n\n /** The typable boxes, in column order — the AM/PM column is a `<span>`, not one of these. */\n private async boxes(): Promise<TestElement[]> {\n return this.locatorForAll('.wr-time-picker__input')();\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, type HarnessLoader, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerDayHarnessFilters } from './interfaces';\nimport { WrDatePickerDayHarness } from './wr-date-picker-day-harness';\nimport { WrTimePanelHarness } from './wr-time-panel-harness';\n\n/** The first element whose trimmed text is exactly `text`. */\nasync function byText(elements: TestElement[], text: string): Promise<TestElement | null> {\n for (const element of elements) {\n if ((await element.text()) === text) return element;\n }\n return null;\n}\n\n/**\n * Everything `<wr-date-picker>` and `<wr-date-range-picker>` do identically: the\n * trigger button, the popup it owns, and the `<wr-calendar>` inside it. Both\n * components render the SAME trigger element (`.wr-date-picker__trigger` — the\n * range picker reuses the class rather than minting its own) and both publish\n * their popup's id through the trigger's `aria-controls`, so the scoping story is\n * shared too.\n *\n * Not exported from the entry point — a consumer works with one of the two\n * concrete harnesses.\n *\n * @internal\n */\nexport abstract class WrDatePickerHarnessBase extends ComponentHarness {\n /** Concrete class name, so errors read like the harness the caller reached for. */\n protected abstract readonly harnessName: string;\n\n /**\n * Whether the popup is up.\n *\n * Read from the trigger's `aria-expanded` rather than from a host class: unlike\n * `wr-select`, neither picker carries an `--open` modifier, and the ARIA state\n * is the thing a consumer's users actually get told.\n */\n async isOpen(): Promise<boolean> {\n return (await (await this.trigger()).getAttribute('aria-expanded')) === 'true';\n }\n\n /** Open the popup from the trigger. An already-open picker is left alone. */\n async open(): Promise<void> {\n if (await this.isOpen()) return;\n\n await (await this.trigger()).click();\n if (await this.isOpen()) return;\n\n throw new Error(\n `${this.harnessName}.open(): the trigger did not open a popup. A disabled picker refuses to open, and so ` +\n 'does a readonly <wr-date-range-picker> — a readonly <wr-date-picker>, deliberately, still opens.'\n );\n }\n\n /**\n * Close the popup by clicking the trigger again. A closed picker is left alone.\n *\n * Deliberately not Escape: the CDK dispatches a keydown to the TOP-MOST overlay,\n * so with two pickers open this would close whichever opened last rather than\n * this one. The trigger only ever toggles its own popup.\n */\n async close(): Promise<void> {\n if (!(await this.isOpen())) return;\n\n await (await this.trigger()).click();\n }\n\n /** The popup's role — `'dialog'`, which is what the trigger's `aria-haspopup` promises. */\n async getPanelRole(): Promise<string | null> {\n return (await this.panel()).getAttribute('role');\n }\n\n /**\n * The popup's accessible name. Defaults to a catalog string chosen by `mode`\n * (\"Choose date\", \"Choose time\", …) and is overridden by `[panelAriaLabel]`.\n */\n async getPanelAriaLabel(): Promise<string | null> {\n return (await this.panel()).getAttribute('aria-label');\n }\n\n /**\n * Which of the calendar's three views is showing. The header label zooms out\n * (day → month → year); picking a month or a year zooms back in.\n */\n async getView(): Promise<'day' | 'month' | 'year'> {\n if (await this.inPanel('[role=\"grid\"]')) return 'day';\n if (await this.inPanel('.wr-calendar__months')) return 'month';\n if (await this.inPanel('.wr-calendar__years')) return 'year';\n\n throw new Error(this.noCalendar('getView'));\n }\n\n /**\n * The calendar's header: `'January 2025'` in the day view, `'2025'` in the month\n * view, `'2016 – 2027'` in the year view.\n */\n async getPanelHeader(): Promise<string> {\n const label = await this.inPanel('.wr-calendar__label');\n if (!label) throw new Error(this.noCalendar('getPanelHeader'));\n\n return label.text();\n }\n\n /** The weekday strip, in display order — `role=\"columnheader\"` cells of the grid's first row. */\n async getWeekdayLabels(): Promise<string[]> {\n const cells = await this.allInPanel('[role=\"columnheader\"]');\n if (cells.length === 0) {\n throw new Error(\n `${this.harnessName}.getWeekdayLabels(): no weekday strip. Only the day view has one, and mode=\"time\" ` +\n 'has no calendar at all.'\n );\n }\n\n return Promise.all(cells.map(cell => cell.text()));\n }\n\n /**\n * Step the calendar forwards. What one step MEANS follows the active view, as\n * the button's own `aria-label` says: a month in the day view, a year in the\n * month view, twelve years in the year view.\n */\n async next(): Promise<void> {\n await this.navigate('next');\n }\n\n /** Step the calendar backwards — see {@link next} for what one step covers. */\n async previous(): Promise<void> {\n await this.navigate('prev');\n }\n\n /**\n * Zoom out one level by clicking the header, the way a user does: day → month,\n * month → year. Throws at the year view, where the header is inert.\n */\n async zoomOut(): Promise<void> {\n const label = await this.inPanel('.wr-calendar__label');\n if (!label) throw new Error(this.noCalendar('zoomOut'));\n\n if (await label.getProperty<boolean>('disabled')) {\n throw new Error(\n `${this.harnessName}.zoomOut(): the header is inert — the calendar is already showing the year view ` +\n '(or the whole picker is disabled), and there is nothing further to zoom out to.'\n );\n }\n\n await label.click();\n }\n\n /** Pick a month in the month view, by 0-based index or by the label the calendar renders. */\n async selectMonth(month: number | string): Promise<void> {\n const chips = await this.allInPanel('.wr-calendar__months .wr-calendar__chip');\n if (chips.length === 0) {\n throw new Error(\n `${this.harnessName}.selectMonth(): the calendar is not showing its month view — call zoomOut() first.`\n );\n }\n\n const chip = typeof month === 'number' ? chips[month] : await byText(chips, month);\n if (!chip) {\n throw new Error(\n `${this.harnessName}.selectMonth(${JSON.stringify(month)}): no such month. Pass a 0-based index, or the ` +\n 'label the calendar renders — a short month name in the active locale.'\n );\n }\n\n await chip.click();\n }\n\n /** Pick a year in the year view. Only the twelve on show can be picked. */\n async selectYear(year: number): Promise<void> {\n const chips = await this.allInPanel('.wr-calendar__years .wr-calendar__chip');\n if (chips.length === 0) {\n throw new Error(\n `${this.harnessName}.selectYear(): the calendar is not showing its year view — call zoomOut() until ` +\n 'getView() answers \"year\".'\n );\n }\n\n const chip = await byText(chips, String(year));\n if (!chip) {\n const shown = await Promise.all(chips.map(candidate => candidate.text()));\n throw new Error(\n `${this.harnessName}.selectYear(${year}): the year view is showing ${shown[0]}-${shown[shown.length - 1]} ` +\n '— call previous() / next() to move the twelve-year window first.'\n );\n }\n\n await chip.click();\n }\n\n /**\n * The day cells of the calendar in this picker's popup, in DOM order — 42 of\n * them, since the grid is always six weeks and pads into the neighbouring\n * months.\n */\n async getDays(filters: WrDatePickerDayHarnessFilters = {}): Promise<WrDatePickerDayHarness[]> {\n await this.requireGrid('getDays');\n\n return (await this.panelLoader()).getAllHarnesses(WrDatePickerDayHarness.with(filters));\n }\n\n /**\n * One day of the month on show. The padding days of the neighbouring months are\n * skipped, so `getDay(1)` is the 1st of THIS month and never the 1st of the next.\n */\n async getDay(dayOfMonth: number): Promise<WrDatePickerDayHarness> {\n const cells = await this.getDays({ text: String(dayOfMonth) });\n for (const cell of cells) {\n if (!(await cell.isOutOfMonth())) return cell;\n }\n\n throw new Error(\n `${this.harnessName}.getDay(${dayOfMonth}): the month on show (${await this.getPanelHeader()}) has no such ` +\n 'day. Cells belonging to the neighbouring months are deliberately not matched — call previous() / next() ' +\n 'to change month.'\n );\n }\n\n /** Click a day of the month on show. */\n async selectDay(dayOfMonth: number): Promise<void> {\n const cell = await this.getDay(dayOfMonth);\n if (await cell.isDisabled()) {\n throw new Error(\n `${this.harnessName}.selectDay(${dayOfMonth}): that day is disabled — min / max or dateFilter rules it ` +\n 'out, and a disabled cell swallows the click rather than picking anything.'\n );\n }\n\n await cell.click();\n }\n\n /** The trigger button. Both components render it with the same class. */\n protected async trigger(): Promise<TestElement> {\n return this.locatorFor('.wr-date-picker__trigger')();\n }\n\n /**\n * The id of THIS picker's popup.\n *\n * The trigger publishes it as `aria-controls` only while the popup is up, which\n * makes one lookup answer both questions — is it open, and which of the panes\n * in the shared overlay container is ours.\n */\n protected async panelId(): Promise<string> {\n const id = await (await this.trigger()).getAttribute('aria-controls');\n if (!id) {\n throw new Error(\n `${this.harnessName}: the popup is closed — call open() first. The trigger only publishes aria-controls ` +\n 'while its popup is up.'\n );\n }\n\n return id;\n }\n\n /** A loader scoped to THIS picker's popup, so a second open picker cannot answer for it. */\n protected async panelLoader(): Promise<HarnessLoader> {\n return this.documentRootLocatorFactory().harnessLoaderFor(`#${await this.panelId()}`);\n }\n\n /** One element inside THIS picker's popup, or `null`. */\n protected async inPanel(selector: string): Promise<TestElement | null> {\n return this.documentRootLocatorFactory().locatorForOptional(`#${await this.panelId()} ${selector}`)();\n }\n\n /** Every matching element inside THIS picker's popup, in DOM order. */\n protected async allInPanel(selector: string): Promise<TestElement[]> {\n return this.documentRootLocatorFactory().locatorForAll(`#${await this.panelId()} ${selector}`)();\n }\n\n /**\n * The time steppers in this popup, in DOM order — none in `date` mode, one for\n * `time` / `datetime`, and two (start then end) for a datetime range.\n */\n protected async timePanels(): Promise<WrTimePanelHarness[]> {\n return (await this.panelLoader()).getAllHarnesses(WrTimePanelHarness);\n }\n\n private async navigate(direction: 'prev' | 'next'): Promise<void> {\n const button = await this.inPanel(`.wr-calendar__nav--${direction}`);\n if (!button) throw new Error(this.noCalendar(direction === 'next' ? 'next' : 'previous'));\n\n await button.click();\n }\n\n /** Fail early on the day-cell methods rather than answering with an empty list. */\n private async requireGrid(method: string): Promise<void> {\n if (await this.inPanel('[role=\"grid\"]')) return;\n\n throw new Error(\n `${this.harnessName}.${method}(): this popup has no day grid. mode=\"time\" renders a time stepper only, and a ` +\n 'calendar zoomed out to its month / year view offers chips instead of day cells — selectMonth() / ' +\n 'selectYear() come back to the days.'\n );\n }\n\n private async panel(): Promise<TestElement> {\n return this.documentRootLocatorFactory().locatorFor(`#${await this.panelId()}`)();\n }\n\n private noCalendar(method: string): string {\n return `${this.harnessName}.${method}(): this popup has no calendar — mode=\"time\" renders a time stepper only.`;\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerHarnessFilters, WrDatePickerTimeFields } from './interfaces';\nimport { WrDatePickerHarnessBase } from './wr-date-picker-harness-base';\nimport type { WrTimePanelHarness } from './wr-time-panel-harness';\n\n/**\n * Test harness for `<wr-date-picker>` — the `date`, `time` and `datetime` modes\n * of the one component.\n *\n * The popup is NOT inside the picker: it is a pane in the overlay container, a\n * sibling of the whole app. Everything inside it is therefore reached through the\n * document root, scoped by the id the trigger publishes as `aria-controls` —\n * which is what keeps two pickers on one page from reading each other's calendar.\n *\n * The value lives in a text field, so there are two ways in and the harness\n * offers both: {@link setValueText} types a date, {@link selectDay} picks one out\n * of the grid. There is deliberately no clear BUTTON on this component — an\n * emptied field is what clears the value, and that is what {@link clear} does.\n *\n * @example\n * ```ts\n * const picker = await loader.getHarness(WrDatePickerHarness);\n *\n * await picker.open();\n * expect(await picker.getPanelHeader()).toBe('January 2025');\n * await picker.selectDay(14);\n *\n * expect(await picker.getValueText()).toBe('14.01.2025');\n * expect(await (await picker.getDay(14)).isSelected()).toBe(true);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDatePickerHarness extends WrDatePickerHarnessBase {\n static hostSelector = 'wr-date-picker';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrDatePickerHarnessFilters = {}): HarnessPredicate<WrDatePickerHarness> {\n return new HarnessPredicate(WrDatePickerHarness, options)\n .addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getValueText(), text))\n .addOption('placeholder', options.placeholder, (harness, placeholder) =>\n HarnessPredicate.stringMatches(harness.getPlaceholder(), placeholder)\n )\n .addOption('mode', options.mode, async (harness, mode) => (await harness.getMode()) === mode)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n protected readonly harnessName = 'WrDatePickerHarness';\n\n /** Which mode the picker is in, read from the `wr-date-picker--*` modifier. */\n async getMode(): Promise<'date' | 'time' | 'datetime'> {\n const host = await this.host();\n if (await host.hasClass('wr-date-picker--time')) return 'time';\n if (await host.hasClass('wr-date-picker--datetime')) return 'datetime';\n return 'date';\n }\n\n /**\n * What the field is showing: the value in the picker's `format`, or `''` when it\n * is empty and the placeholder is up.\n */\n async getValueText(): Promise<string> {\n return (await this.field()).getProperty<string>('value');\n }\n\n /** The field's placeholder. */\n async getPlaceholder(): Promise<string> {\n return (await this.field()).getProperty<string>('placeholder');\n }\n\n /**\n * Type a date into the field, replacing whatever is there.\n *\n * The field is cleared first, so the value passes through `null` on the way —\n * exactly as it does for a user who selects all and types over it. The picker\n * parses on every keystroke and commits only when the text parses in full, so a\n * partial or out-of-bounds entry leaves the model on its last good value.\n */\n async setValueText(text: string): Promise<void> {\n const field = await this.field();\n await field.clear();\n // `sendKeys()` with an empty string throws — clearing was the whole request.\n if (text) await field.sendKeys(text);\n }\n\n /**\n * Empty the field, which clears the value.\n *\n * There is no clear button to click: unlike `wr-select`, this component ships no\n * `×` affordance, and an empty field is the documented way to get back to `null`.\n */\n async clear(): Promise<void> {\n await (await this.field()).clear();\n }\n\n /** Whether the picker refuses interaction. */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-date-picker--disabled');\n }\n\n /**\n * Whether the field refuses typing. A read-only picker still OPENS its popup —\n * documented behaviour, and the one place it parts company with the range picker.\n */\n async isReadonly(): Promise<boolean> {\n return (await this.field()).getProperty<boolean>('readOnly');\n }\n\n /** Move focus to the field. */\n async focus(): Promise<void> {\n return (await this.field()).focus();\n }\n\n /**\n * Blur the field, which is what reformats a value to canonical and emits `touch`\n * so a bound `[formField]` can mark itself touched.\n */\n async blur(): Promise<void> {\n return (await this.field()).blur();\n }\n\n /** Whether the field currently has focus. */\n async isFocused(): Promise<boolean> {\n return (await this.field()).isFocused();\n }\n\n /**\n * The open time stepper's value as `HH:MM`, plus `:SS` when `showSeconds` is on\n * and ` AM`/` PM` in 12-hour mode.\n */\n async getTime(): Promise<string> {\n return (await this.timePanel()).getTimeText();\n }\n\n /** Type into the open time stepper's boxes. Omitted fields are left alone. */\n async setTime(fields: WrDatePickerTimeFields): Promise<void> {\n await (await this.timePanel()).setFields(fields);\n }\n\n /** Click one of the stepper's ▲ / ▼ buttons. Minutes and seconds move by `step`. */\n async stepTime(unit: 'hours' | 'minutes' | 'seconds', direction: 1 | -1): Promise<void> {\n await (await this.timePanel()).step(unit, direction);\n }\n\n /** Flip the stepper between AM and PM. Throws in 24-hour mode, which has no such control. */\n async toggleMeridiem(): Promise<void> {\n await (await this.timePanel()).toggleMeridiem();\n }\n\n private async field(): Promise<TestElement> {\n return this.locatorFor('input[wrInput]')();\n }\n\n /** The one stepper in the popup — `panelId()` has already refused a closed picker. */\n private async timePanel(): Promise<WrTimePanelHarness> {\n const [panel] = await this.timePanels();\n if (!panel) {\n throw new Error(\n `${this.harnessName}: this popup has no time stepper — mode=\"date\" renders a calendar only. Use mode=\"time\" ` +\n 'or mode=\"datetime\".'\n );\n }\n\n return panel;\n }\n}\n","/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDatePickerTimeFields, WrDateRangePickerHarnessFilters } from './interfaces';\nimport { WrDatePickerHarnessBase } from './wr-date-picker-harness-base';\nimport type { WrTimePanelHarness } from './wr-time-panel-harness';\n\n/** Which end of the range a call applies to. */\ntype End = 'start' | 'end';\n\n/**\n * Test harness for `<wr-date-range-picker>` — two text fields over one range\n * calendar.\n *\n * A separate harness rather than a mode of {@link WrDatePickerHarness}, because\n * the DOM a spec has to reach is genuinely different: two fields with a separator\n * between them, and in `datetime` mode two time steppers instead of one. Every\n * call that could mean either field takes an explicit `'start'` / `'end'` — the\n * component's own bugs were nearly all \"which end did that belong to\", and a\n * harness that guessed would reproduce them.\n *\n * The popup, the calendar and the scoping work exactly as they do for the single\n * picker: a pane in the overlay container, addressed by the id the trigger\n * publishes as `aria-controls`.\n *\n * @example\n * ```ts\n * const period = await loader.getHarness(WrDateRangePickerHarness);\n *\n * await period.open();\n * await period.selectDay(14);\n * await period.selectDay(20);\n *\n * expect([await period.getStartText(), await period.getEndText()]).toEqual(['14.01.2025', '20.01.2025']);\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDateRangePickerHarness extends WrDatePickerHarnessBase {\n static hostSelector = 'wr-date-range-picker';\n\n /** Build a predicate that narrows the query. */\n static with(options: WrDateRangePickerHarnessFilters = {}): HarnessPredicate<WrDateRangePickerHarness> {\n return new HarnessPredicate(WrDateRangePickerHarness, options)\n .addOption('startText', options.startText, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getStartText(), text)\n )\n .addOption('endText', options.endText, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getEndText(), text)\n )\n .addOption('mode', options.mode, async (harness, mode) => (await harness.getMode()) === mode)\n .addOption('disabled', options.disabled, async (harness, disabled) => (await harness.isDisabled()) === disabled)\n .addOption('open', options.open, async (harness, open) => (await harness.isOpen()) === open);\n }\n\n protected readonly harnessName = 'WrDateRangePickerHarness';\n\n /** Which mode the picker is in, read from the `wr-date-range-picker--*` modifier. */\n async getMode(): Promise<'date' | 'datetime'> {\n return (await (await this.host()).hasClass('wr-date-range-picker--datetime')) ? 'datetime' : 'date';\n }\n\n /** What the start field is showing — `''` while that end is unpicked. */\n async getStartText(): Promise<string> {\n return (await this.field('start')).getProperty<string>('value');\n }\n\n /** What the end field is showing — `''` while that end is unpicked. */\n async getEndText(): Promise<string> {\n return (await this.field('end')).getProperty<string>('value');\n }\n\n /** The glyph rendered between the two fields (`[separator]`, `'–'` by default). */\n async getSeparator(): Promise<string> {\n return (await this.locatorFor('.wr-date-range-picker__separator')()).text();\n }\n\n /** The placeholder of one field. */\n async getPlaceholder(end: End): Promise<string> {\n return (await this.field(end)).getProperty<string>('placeholder');\n }\n\n /**\n * Type a date into the start field, replacing whatever is there.\n *\n * Ends typed out of order are NOT swapped here — the component settles the\n * ordering when the interaction is over, so call {@link blur} (or close the\n * popup) to see a normalised range.\n */\n async setStartText(text: string): Promise<void> {\n await this.type('start', text);\n }\n\n /** Type a date into the end field — see {@link setStartText} on ordering. */\n async setEndText(text: string): Promise<void> {\n await this.type('end', text);\n }\n\n /** Empty both fields, which clears the range. Neither end has a clear button. */\n async clear(): Promise<void> {\n await (await this.field('start')).clear();\n await (await this.field('end')).clear();\n }\n\n /** Whether the picker refuses interaction. */\n async isDisabled(): Promise<boolean> {\n return (await this.host()).hasClass('wr-date-range-picker--disabled');\n }\n\n /**\n * Whether the fields refuse typing. Stricter than `<wr-date-picker>`: a\n * read-only range picker also refuses to open, since two fields over one\n * calendar leaves no reading of \"untypeable\" that still lets the grid rewrite\n * both ends.\n */\n async isReadonly(): Promise<boolean> {\n return (await this.field('start')).getProperty<boolean>('readOnly');\n }\n\n /** Move focus to one field. */\n async focus(end: End): Promise<void> {\n return (await this.field(end)).focus();\n }\n\n /**\n * Blur one field. This is the moment an out-of-order range is put back in\n * order, and — when focus leaves the control rather than hopping to the other\n * end — the moment `touch` is emitted.\n */\n async blur(end: End): Promise<void> {\n return (await this.field(end)).blur();\n }\n\n /** Whether one field currently has focus. */\n async isFocused(end: End): Promise<boolean> {\n return (await this.field(end)).isFocused();\n }\n\n /**\n * One end's time stepper as `HH:MM`, plus `:SS` when `showSeconds` is on and\n * ` AM`/` PM` in 12-hour mode. `datetime` mode only.\n */\n async getTime(end: End): Promise<string> {\n return (await this.timePanel(end)).getTimeText();\n }\n\n /** Type into one end's time stepper. Omitted fields are left alone. */\n async setTime(end: End, fields: WrDatePickerTimeFields): Promise<void> {\n await (await this.timePanel(end)).setFields(fields);\n }\n\n /** Click one of an end's ▲ / ▼ buttons. Minutes and seconds move by `step`. */\n async stepTime(end: End, unit: 'hours' | 'minutes' | 'seconds', direction: 1 | -1): Promise<void> {\n await (await this.timePanel(end)).step(unit, direction);\n }\n\n /** Flip one end between AM and PM. Throws in 24-hour mode, which has no such control. */\n async toggleMeridiem(end: End): Promise<void> {\n await (await this.timePanel(end)).toggleMeridiem();\n }\n\n private async type(end: End, text: string): Promise<void> {\n const field = await this.field(end);\n await field.clear();\n // `sendKeys()` with an empty string throws — clearing was the whole request.\n if (text) await field.sendKeys(text);\n }\n\n /** The two fields are addressed by position, which is the order they render in. */\n private async field(end: End): Promise<TestElement> {\n const fields = await this.locatorForAll('.wr-date-range-picker__input')();\n const field = fields[end === 'start' ? 0 : 1];\n if (!field) {\n throw new Error(`${this.harnessName}: no \"${end}\" field — the picker rendered ${fields.length} of them.`);\n }\n\n return field;\n }\n\n /**\n * One end's stepper. Both are mounted together in `datetime` mode, start first —\n * the group labels are localized, so position is the addressable thing.\n */\n private async timePanel(end: End): Promise<WrTimePanelHarness> {\n const panels = await this.timePanels();\n if (panels.length === 0) {\n throw new Error(\n `${this.harnessName}: this popup has no time steppers — mode=\"date\" renders a calendar only. Use ` +\n 'mode=\"datetime\".'\n );\n }\n\n const panel = panels[end === 'start' ? 0 : 1];\n if (!panel) {\n throw new Error(`${this.harnessName}: no \"${end}\" time stepper — the popup rendered ${panels.length}.`);\n }\n\n return panel;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["WrDatePickerDayHarness"],"mappings":"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"}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { ComponentHarness, HarnessPredicate } from '@angular/cdk/testing';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
|
|
8
|
+
*/
|
|
9
|
+
/** The readable copy of the string, carried once and hidden with CSS. */
|
|
10
|
+
const SR_ONLY = '.wr-decrypt-text__sr-only';
|
|
11
|
+
/** One span per CODE POINT of whatever is on screen this frame. */
|
|
12
|
+
const CHAR = '.wr-decrypt-text__char';
|
|
13
|
+
/** The modifier that carries the entire progress model. */
|
|
14
|
+
const ENCRYPTED = '.wr-decrypt-text__char--encrypted';
|
|
15
|
+
/**
|
|
16
|
+
* The wrapper holding every scramble character.
|
|
17
|
+
*
|
|
18
|
+
* Addressed structurally because it is the one element in this template with no BEM
|
|
19
|
+
* class of its own — see {@link WrDecryptTextHarness.isAnimatedLayerHidden}, which
|
|
20
|
+
* exists precisely because a selector this fragile should be written down once rather
|
|
21
|
+
* than assumed in every spec.
|
|
22
|
+
*/
|
|
23
|
+
const SCRAMBLE = `${SR_ONLY} + span`;
|
|
24
|
+
/** Exactly what the element holds — untrimmed, unlike `TestElement.text()`. */
|
|
25
|
+
async function rawText(element) {
|
|
26
|
+
return (await element.getProperty('textContent')) ?? '';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Test harness for `<wr-decrypt-text>`.
|
|
30
|
+
*
|
|
31
|
+
* **Two layers, two questions, and they are meant to disagree.** The component renders
|
|
32
|
+
* the real string once in a visually-hidden span and the scramble in a parallel
|
|
33
|
+
* `aria-hidden` layer, so {@link getAccessibleText} is what a screen reader hears and
|
|
34
|
+
* {@link getRenderedText} is what a sighted reader sees. Mid-reveal the second is
|
|
35
|
+
* literal gibberish while the first must be untouched — that is the contract, and it is
|
|
36
|
+
* the half nobody looks at. A spec asserting only the visible glyphs would pass on a
|
|
37
|
+
* component that announced `XQ#PZ`.
|
|
38
|
+
*
|
|
39
|
+
* **Prefer the class over the glyphs.** {@link getRevealedIndices},
|
|
40
|
+
* {@link getEncryptedCount} and {@link isFullyRevealed} read
|
|
41
|
+
* `.wr-decrypt-text__char--encrypted`, which is deterministic with no stubbing at all —
|
|
42
|
+
* they are what tell `revealDirection` `'start'` from `'end'` from `'center'`, one index
|
|
43
|
+
* per tick. {@link getRenderedText} reads the glyphs themselves, which are a fresh
|
|
44
|
+
* `Math.random()` draw per tick, so stub it (`vi.spyOn(Math, 'random')
|
|
45
|
+
* .mockReturnValue(0)`) before asserting a string.
|
|
46
|
+
*
|
|
47
|
+
* **The clock is the spec's, not the harness's.** The reveal is a `setInterval` this
|
|
48
|
+
* class does not own, so there is no `advance(ms)` and no `waitUntilRevealed()`: one
|
|
49
|
+
* would flake on a loaded machine and the other would silently require fake timers to
|
|
50
|
+
* have been installed. Fake `setInterval` / `clearInterval` and nothing else — faking
|
|
51
|
+
* the microtask queue freezes the stabilization every `await harness.*()` runs through —
|
|
52
|
+
* then advance the clock yourself between reads.
|
|
53
|
+
*
|
|
54
|
+
* **There is no keyboard path, and this harness does not pretend there is one.**
|
|
55
|
+
* {@link hover}, {@link mouseAway} and {@link click} are the component's only triggers;
|
|
56
|
+
* with `animateOn="click"` the host takes a click handler while carrying no role, no
|
|
57
|
+
* `tabindex` and no keydown handler, so a `focus()` / `sendKeys(Enter)` affordance here
|
|
58
|
+
* would make a spec pass while a keyboard or switch user could not reveal the text at
|
|
59
|
+
* all. There is no `enterViewport()` either: `'view'` and `'inViewHover'` hang off an
|
|
60
|
+
* `IntersectionObserver` jsdom does not implement, so the spec stubs the global and
|
|
61
|
+
* fires it.
|
|
62
|
+
*
|
|
63
|
+
* **Also deliberately absent:** `isAnimating()` — the component has such a signal and it
|
|
64
|
+
* never reaches the DOM, and deriving it from "something is encrypted" would be wrong in
|
|
65
|
+
* exactly the interesting case, since `animateOn="click"` rests fully encrypted and
|
|
66
|
+
* perfectly still. `getSpeed()` / `getMaxIterations()` / `getRevealDirection()` /
|
|
67
|
+
* `getClickMode()` / `getAnimateOn()` — inputs that are never reflected anywhere, so
|
|
68
|
+
* reporting them would mean reading the component instance rather than the rendered
|
|
69
|
+
* control; they are observable as behaviour, which is what the methods above measure.
|
|
70
|
+
* `getScrambleAlphabet()` — the pool is internal and each frame shows one random draw
|
|
71
|
+
* from it, so any reconstruction is a guess that gets worse the shorter the string.
|
|
72
|
+
* `getCharColors()` / `isDimmed()` — the `--encrypted` rule is deliberately EMPTY, a
|
|
73
|
+
* styling hook for consumers, so there is nothing painted to read in any environment.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const secret = await loader.getHarness(WrDecryptTextHarness.with({ text: 'Hello' }));
|
|
78
|
+
*
|
|
79
|
+
* await secret.hover();
|
|
80
|
+
* vi.advanceTimersByTime(50);
|
|
81
|
+
*
|
|
82
|
+
* expect(await secret.getRevealedIndices()).toEqual([0]); // sequential, from the start
|
|
83
|
+
* expect(await secret.getAccessibleText()).toBe('Hello'); // untouched throughout
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @see https://ngwr.dev/guides/testing
|
|
87
|
+
*/
|
|
88
|
+
class WrDecryptTextHarness extends ComponentHarness {
|
|
89
|
+
static hostSelector = 'wr-decrypt-text';
|
|
90
|
+
/** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */
|
|
91
|
+
static with(options = {}) {
|
|
92
|
+
return new HarnessPredicate(WrDecryptTextHarness, options)
|
|
93
|
+
.addOption('text', options.text, (harness, text) => HarnessPredicate.stringMatches(harness.getAccessibleText(), text))
|
|
94
|
+
.addOption('revealed', options.revealed, async (harness, revealed) => {
|
|
95
|
+
// Not `isFullyRevealed()`, which refuses to answer for an instance that rendered
|
|
96
|
+
// no characters. A predicate is the wrong place to throw — a single empty
|
|
97
|
+
// instance would take down every query on the page — so the vacuous case is
|
|
98
|
+
// spelled out here instead, and matches neither `true` nor `false`.
|
|
99
|
+
if ((await harness.getCharCount()) === 0)
|
|
100
|
+
return false;
|
|
101
|
+
return ((await harness.getEncryptedCount()) === 0) === revealed;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
chars = this.locatorForAll(CHAR);
|
|
105
|
+
encrypted = this.locatorForAll(ENCRYPTED);
|
|
106
|
+
/**
|
|
107
|
+
* The real string, as the one readable copy holds it.
|
|
108
|
+
*
|
|
109
|
+
* The single highest-value assertion here: it must stay equal to the `text` input on
|
|
110
|
+
* every frame, while the visible layer is unreadable by design. Lose this span and the
|
|
111
|
+
* component announces nothing at all — every drawn character sits under `aria-hidden`,
|
|
112
|
+
* so no other part of the DOM would change.
|
|
113
|
+
*
|
|
114
|
+
* Untrimmed, so that it can be compared against {@link getRenderedText} without one
|
|
115
|
+
* side of the comparison quietly forgiving a lost leading space.
|
|
116
|
+
*/
|
|
117
|
+
async getAccessibleText() {
|
|
118
|
+
return rawText(await this.locatorFor(SR_ONLY)());
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* What is on screen this frame — scramble glyphs and all, in document order.
|
|
122
|
+
*
|
|
123
|
+
* Plain before anything triggers it, gibberish mid-reveal, and the real string once it
|
|
124
|
+
* settles; comparing it against {@link getAccessibleText} is how a spec says which of
|
|
125
|
+
* those it is looking at. `useOriginalCharsOnly` is visible only here — every glyph
|
|
126
|
+
* has to be one the text really contains.
|
|
127
|
+
*
|
|
128
|
+
* Nondeterministic unless the spec stubs `Math.random`, because each unrevealed
|
|
129
|
+
* position is an independent draw from the pool. When the assertion does not actually
|
|
130
|
+
* need the glyphs, {@link getRevealedIndices} answers the same progress question off
|
|
131
|
+
* the class and needs no stub.
|
|
132
|
+
*/
|
|
133
|
+
async getRenderedText() {
|
|
134
|
+
return (await Promise.all((await this.chars()).map(rawText))).join('');
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* How many characters the animation walks.
|
|
138
|
+
*
|
|
139
|
+
* CODE POINTS, not UTF-16 units: `'a🚀b'` is 3 here, and the indices
|
|
140
|
+
* {@link getRevealedIndices} returns line up with `[...text]` rather than with plain
|
|
141
|
+
* `text[i]`. That distinction is not pedantry — counting units gave a sequential
|
|
142
|
+
* reveal extra ticks with no character to spend them on, and made "everything is
|
|
143
|
+
* revealed" disagree with what was on screen.
|
|
144
|
+
*/
|
|
145
|
+
async getCharCount() {
|
|
146
|
+
return (await this.chars()).length;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The positions that have settled on their real character, ascending.
|
|
150
|
+
*
|
|
151
|
+
* The payload method, and the deterministic one: it reads the `--encrypted` modifier
|
|
152
|
+
* rather than the glyphs, so no `Math.random` stub is involved. It is the only thing
|
|
153
|
+
* that separates `revealDirection` `'start'` (`[0]`, `[0, 1]`, …) from `'end'`
|
|
154
|
+
* (`[4]`, `[3, 4]`, …) from `'center'` (`[2]`, `[1, 2]`, …), and one index per tick is
|
|
155
|
+
* what makes a sequential reveal sequential.
|
|
156
|
+
*
|
|
157
|
+
* Ascending by position, NOT in the order the reveal visited them — the DOM records
|
|
158
|
+
* which characters have landed, never the path taken to them. Assert the set after
|
|
159
|
+
* each tick to pin an order.
|
|
160
|
+
*/
|
|
161
|
+
async getRevealedIndices() {
|
|
162
|
+
const chars = await this.chars();
|
|
163
|
+
const states = await Promise.all(chars.map(char => char.hasClass('wr-decrypt-text__char--encrypted')));
|
|
164
|
+
return states.flatMap((isEncrypted, index) => (isEncrypted ? [] : [index]));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* How many characters are still scrambled.
|
|
168
|
+
*
|
|
169
|
+
* The coarse progress read, and the cheapest way to say "scrambled on hover", "plain on
|
|
170
|
+
* leave", or "a reader who asked for less motion never saw a scramble at all". Class-
|
|
171
|
+
* based like {@link getRevealedIndices}, so it too is random-proof.
|
|
172
|
+
*/
|
|
173
|
+
async getEncryptedCount() {
|
|
174
|
+
return (await this.encrypted()).length;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Whether every character has settled.
|
|
178
|
+
*
|
|
179
|
+
* Deliberately does NOT also compare {@link getRenderedText} against
|
|
180
|
+
* {@link getAccessibleText}: folding two conditions into one boolean hides which of
|
|
181
|
+
* them broke, and a spec that wants both should write both lines.
|
|
182
|
+
*
|
|
183
|
+
* Throws when nothing was rendered. "No character is encrypted" is true of no
|
|
184
|
+
* characters, which is the same answer a component that stopped rendering its text
|
|
185
|
+
* would give — and this is the method most likely to be the only assertion in a test.
|
|
186
|
+
*/
|
|
187
|
+
async isFullyRevealed() {
|
|
188
|
+
const total = await this.getCharCount();
|
|
189
|
+
if (total === 0) {
|
|
190
|
+
throw new Error('WrDecryptTextHarness.isFullyRevealed(): this <wr-decrypt-text> rendered no characters, so "nothing is ' +
|
|
191
|
+
'encrypted" is vacuously true — the same answer a component that stopped rendering its text would ' +
|
|
192
|
+
'give. Check `text` is set, or ask getCharCount().');
|
|
193
|
+
}
|
|
194
|
+
return (await this.getEncryptedCount()) === 0;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Whether the scramble layer is out of the accessibility tree.
|
|
198
|
+
*
|
|
199
|
+
* The contract that keeps a screen reader from spelling out `XQ#PZ` beside the real
|
|
200
|
+
* string it already read. Worth a method rather than an assumption because the wrapper
|
|
201
|
+
* holding the characters carries NO class — it can only be addressed as the readable
|
|
202
|
+
* span's next sibling, which any template reshuffle breaks.
|
|
203
|
+
*
|
|
204
|
+
* Throws when that structural selector finds nothing, rather than returning `false`: a
|
|
205
|
+
* missing wrapper and an unhidden one are different failures, and the answer to
|
|
206
|
+
* "is the scramble hidden" when there is no scramble layer at all is not `false`.
|
|
207
|
+
*/
|
|
208
|
+
async isAnimatedLayerHidden() {
|
|
209
|
+
const scramble = await this.locatorForOptional(SCRAMBLE)();
|
|
210
|
+
if (!scramble) {
|
|
211
|
+
throw new Error('WrDecryptTextHarness.isAnimatedLayerHidden(): no scramble layer found. It carries no class of its own, ' +
|
|
212
|
+
`so it is addressed structurally as \`${SCRAMBLE}\` — a template that moved either span out from ` +
|
|
213
|
+
'under that selector breaks this read, and "false" would report the layer as ANNOUNCED rather than as ' +
|
|
214
|
+
'missing.');
|
|
215
|
+
}
|
|
216
|
+
return (await scramble.getAttribute('aria-hidden')) === 'true';
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Point at it — the trigger for `animateOn` `'hover'` and `'inViewHover'`.
|
|
220
|
+
*
|
|
221
|
+
* Drives the component the way a user does, and works in a test because the trigger is
|
|
222
|
+
* an event rather than a gesture: `TestElement.hover()` dispatches `pointerenter`,
|
|
223
|
+
* `mouseover` and `mouseenter`, and the component listens for the last of those. A
|
|
224
|
+
* hand-rolled pointer-only dispatch would not reach it.
|
|
225
|
+
*
|
|
226
|
+
* Does nothing in the other two modes, exactly as a real pointer does nothing there.
|
|
227
|
+
*/
|
|
228
|
+
async hover() {
|
|
229
|
+
return (await this.host()).hover();
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Move the pointer off — the other half of hover mode.
|
|
233
|
+
*
|
|
234
|
+
* Pins the promise that leaving mid-scramble restores the real text IMMEDIATELY rather
|
|
235
|
+
* than letting the reveal play out: the reader took the effect away, so the string has
|
|
236
|
+
* to come back at once.
|
|
237
|
+
*/
|
|
238
|
+
async mouseAway() {
|
|
239
|
+
return (await this.host()).mouseAway();
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Click the host — the `animateOn="click"` trigger, and the only way to tell
|
|
243
|
+
* `clickMode` `'once'` from `'toggle'`.
|
|
244
|
+
*
|
|
245
|
+
* There is no keyboard equivalent, and that is a statement about the component rather
|
|
246
|
+
* than about this harness: in click mode the host carries a click handler with no role,
|
|
247
|
+
* no `tabindex` and no keydown handler, so a keyboard or switch user cannot reveal the
|
|
248
|
+
* text at all. A `sendKeys(Enter)` here would invent an affordance that does not exist
|
|
249
|
+
* and let a spec pass over the top of it.
|
|
250
|
+
*/
|
|
251
|
+
async click() {
|
|
252
|
+
return (await this.host()).click();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generated bundle index. Do not edit.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
export { WrDecryptTextHarness };
|
|
261
|
+
//# sourceMappingURL=ngwr-decrypt-text-testing.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngwr-decrypt-text-testing.mjs","sources":["../../../projects/lib/decrypt-text/testing/wr-decrypt-text-harness.ts","../../../projects/lib/decrypt-text/testing/ngwr-decrypt-text-testing.ts"],"sourcesContent":["/**\n * @license\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE\n */\n\nimport { ComponentHarness, HarnessPredicate, type TestElement } from '@angular/cdk/testing';\n\nimport type { WrDecryptTextHarnessFilters } from './interfaces';\n\n/** The readable copy of the string, carried once and hidden with CSS. */\nconst SR_ONLY = '.wr-decrypt-text__sr-only';\n\n/** One span per CODE POINT of whatever is on screen this frame. */\nconst CHAR = '.wr-decrypt-text__char';\n\n/** The modifier that carries the entire progress model. */\nconst ENCRYPTED = '.wr-decrypt-text__char--encrypted';\n\n/**\n * The wrapper holding every scramble character.\n *\n * Addressed structurally because it is the one element in this template with no BEM\n * class of its own — see {@link WrDecryptTextHarness.isAnimatedLayerHidden}, which\n * exists precisely because a selector this fragile should be written down once rather\n * than assumed in every spec.\n */\nconst SCRAMBLE = `${SR_ONLY} + span`;\n\n/** Exactly what the element holds — untrimmed, unlike `TestElement.text()`. */\nasync function rawText(element: TestElement): Promise<string> {\n return (await element.getProperty<string | null>('textContent')) ?? '';\n}\n\n/**\n * Test harness for `<wr-decrypt-text>`.\n *\n * **Two layers, two questions, and they are meant to disagree.** The component renders\n * the real string once in a visually-hidden span and the scramble in a parallel\n * `aria-hidden` layer, so {@link getAccessibleText} is what a screen reader hears and\n * {@link getRenderedText} is what a sighted reader sees. Mid-reveal the second is\n * literal gibberish while the first must be untouched — that is the contract, and it is\n * the half nobody looks at. A spec asserting only the visible glyphs would pass on a\n * component that announced `XQ#PZ`.\n *\n * **Prefer the class over the glyphs.** {@link getRevealedIndices},\n * {@link getEncryptedCount} and {@link isFullyRevealed} read\n * `.wr-decrypt-text__char--encrypted`, which is deterministic with no stubbing at all —\n * they are what tell `revealDirection` `'start'` from `'end'` from `'center'`, one index\n * per tick. {@link getRenderedText} reads the glyphs themselves, which are a fresh\n * `Math.random()` draw per tick, so stub it (`vi.spyOn(Math, 'random')\n * .mockReturnValue(0)`) before asserting a string.\n *\n * **The clock is the spec's, not the harness's.** The reveal is a `setInterval` this\n * class does not own, so there is no `advance(ms)` and no `waitUntilRevealed()`: one\n * would flake on a loaded machine and the other would silently require fake timers to\n * have been installed. Fake `setInterval` / `clearInterval` and nothing else — faking\n * the microtask queue freezes the stabilization every `await harness.*()` runs through —\n * then advance the clock yourself between reads.\n *\n * **There is no keyboard path, and this harness does not pretend there is one.**\n * {@link hover}, {@link mouseAway} and {@link click} are the component's only triggers;\n * with `animateOn=\"click\"` the host takes a click handler while carrying no role, no\n * `tabindex` and no keydown handler, so a `focus()` / `sendKeys(Enter)` affordance here\n * would make a spec pass while a keyboard or switch user could not reveal the text at\n * all. There is no `enterViewport()` either: `'view'` and `'inViewHover'` hang off an\n * `IntersectionObserver` jsdom does not implement, so the spec stubs the global and\n * fires it.\n *\n * **Also deliberately absent:** `isAnimating()` — the component has such a signal and it\n * never reaches the DOM, and deriving it from \"something is encrypted\" would be wrong in\n * exactly the interesting case, since `animateOn=\"click\"` rests fully encrypted and\n * perfectly still. `getSpeed()` / `getMaxIterations()` / `getRevealDirection()` /\n * `getClickMode()` / `getAnimateOn()` — inputs that are never reflected anywhere, so\n * reporting them would mean reading the component instance rather than the rendered\n * control; they are observable as behaviour, which is what the methods above measure.\n * `getScrambleAlphabet()` — the pool is internal and each frame shows one random draw\n * from it, so any reconstruction is a guess that gets worse the shorter the string.\n * `getCharColors()` / `isDimmed()` — the `--encrypted` rule is deliberately EMPTY, a\n * styling hook for consumers, so there is nothing painted to read in any environment.\n *\n * @example\n * ```ts\n * const secret = await loader.getHarness(WrDecryptTextHarness.with({ text: 'Hello' }));\n *\n * await secret.hover();\n * vi.advanceTimersByTime(50);\n *\n * expect(await secret.getRevealedIndices()).toEqual([0]); // sequential, from the start\n * expect(await secret.getAccessibleText()).toBe('Hello'); // untouched throughout\n * ```\n *\n * @see https://ngwr.dev/guides/testing\n */\nexport class WrDecryptTextHarness extends ComponentHarness {\n static hostSelector = 'wr-decrypt-text';\n\n /** Build a predicate that narrows the query — pass to `getHarness` / `getAllHarnesses`. */\n static with(options: WrDecryptTextHarnessFilters = {}): HarnessPredicate<WrDecryptTextHarness> {\n return new HarnessPredicate(WrDecryptTextHarness, options)\n .addOption('text', options.text, (harness, text) =>\n HarnessPredicate.stringMatches(harness.getAccessibleText(), text)\n )\n .addOption('revealed', options.revealed, async (harness, revealed) => {\n // Not `isFullyRevealed()`, which refuses to answer for an instance that rendered\n // no characters. A predicate is the wrong place to throw — a single empty\n // instance would take down every query on the page — so the vacuous case is\n // spelled out here instead, and matches neither `true` nor `false`.\n if ((await harness.getCharCount()) === 0) return false;\n return ((await harness.getEncryptedCount()) === 0) === revealed;\n });\n }\n\n private readonly chars = this.locatorForAll(CHAR);\n private readonly encrypted = this.locatorForAll(ENCRYPTED);\n\n /**\n * The real string, as the one readable copy holds it.\n *\n * The single highest-value assertion here: it must stay equal to the `text` input on\n * every frame, while the visible layer is unreadable by design. Lose this span and the\n * component announces nothing at all — every drawn character sits under `aria-hidden`,\n * so no other part of the DOM would change.\n *\n * Untrimmed, so that it can be compared against {@link getRenderedText} without one\n * side of the comparison quietly forgiving a lost leading space.\n */\n async getAccessibleText(): Promise<string> {\n return rawText(await this.locatorFor(SR_ONLY)());\n }\n\n /**\n * What is on screen this frame — scramble glyphs and all, in document order.\n *\n * Plain before anything triggers it, gibberish mid-reveal, and the real string once it\n * settles; comparing it against {@link getAccessibleText} is how a spec says which of\n * those it is looking at. `useOriginalCharsOnly` is visible only here — every glyph\n * has to be one the text really contains.\n *\n * Nondeterministic unless the spec stubs `Math.random`, because each unrevealed\n * position is an independent draw from the pool. When the assertion does not actually\n * need the glyphs, {@link getRevealedIndices} answers the same progress question off\n * the class and needs no stub.\n */\n async getRenderedText(): Promise<string> {\n return (await Promise.all((await this.chars()).map(rawText))).join('');\n }\n\n /**\n * How many characters the animation walks.\n *\n * CODE POINTS, not UTF-16 units: `'a🚀b'` is 3 here, and the indices\n * {@link getRevealedIndices} returns line up with `[...text]` rather than with plain\n * `text[i]`. That distinction is not pedantry — counting units gave a sequential\n * reveal extra ticks with no character to spend them on, and made \"everything is\n * revealed\" disagree with what was on screen.\n */\n async getCharCount(): Promise<number> {\n return (await this.chars()).length;\n }\n\n /**\n * The positions that have settled on their real character, ascending.\n *\n * The payload method, and the deterministic one: it reads the `--encrypted` modifier\n * rather than the glyphs, so no `Math.random` stub is involved. It is the only thing\n * that separates `revealDirection` `'start'` (`[0]`, `[0, 1]`, …) from `'end'`\n * (`[4]`, `[3, 4]`, …) from `'center'` (`[2]`, `[1, 2]`, …), and one index per tick is\n * what makes a sequential reveal sequential.\n *\n * Ascending by position, NOT in the order the reveal visited them — the DOM records\n * which characters have landed, never the path taken to them. Assert the set after\n * each tick to pin an order.\n */\n async getRevealedIndices(): Promise<number[]> {\n const chars = await this.chars();\n const states = await Promise.all(chars.map(char => char.hasClass('wr-decrypt-text__char--encrypted')));\n return states.flatMap((isEncrypted, index) => (isEncrypted ? [] : [index]));\n }\n\n /**\n * How many characters are still scrambled.\n *\n * The coarse progress read, and the cheapest way to say \"scrambled on hover\", \"plain on\n * leave\", or \"a reader who asked for less motion never saw a scramble at all\". Class-\n * based like {@link getRevealedIndices}, so it too is random-proof.\n */\n async getEncryptedCount(): Promise<number> {\n return (await this.encrypted()).length;\n }\n\n /**\n * Whether every character has settled.\n *\n * Deliberately does NOT also compare {@link getRenderedText} against\n * {@link getAccessibleText}: folding two conditions into one boolean hides which of\n * them broke, and a spec that wants both should write both lines.\n *\n * Throws when nothing was rendered. \"No character is encrypted\" is true of no\n * characters, which is the same answer a component that stopped rendering its text\n * would give — and this is the method most likely to be the only assertion in a test.\n */\n async isFullyRevealed(): Promise<boolean> {\n const total = await this.getCharCount();\n\n if (total === 0) {\n throw new Error(\n 'WrDecryptTextHarness.isFullyRevealed(): this <wr-decrypt-text> rendered no characters, so \"nothing is ' +\n 'encrypted\" is vacuously true — the same answer a component that stopped rendering its text would ' +\n 'give. Check `text` is set, or ask getCharCount().'\n );\n }\n\n return (await this.getEncryptedCount()) === 0;\n }\n\n /**\n * Whether the scramble layer is out of the accessibility tree.\n *\n * The contract that keeps a screen reader from spelling out `XQ#PZ` beside the real\n * string it already read. Worth a method rather than an assumption because the wrapper\n * holding the characters carries NO class — it can only be addressed as the readable\n * span's next sibling, which any template reshuffle breaks.\n *\n * Throws when that structural selector finds nothing, rather than returning `false`: a\n * missing wrapper and an unhidden one are different failures, and the answer to\n * \"is the scramble hidden\" when there is no scramble layer at all is not `false`.\n */\n async isAnimatedLayerHidden(): Promise<boolean> {\n const scramble = await this.locatorForOptional(SCRAMBLE)();\n\n if (!scramble) {\n throw new Error(\n 'WrDecryptTextHarness.isAnimatedLayerHidden(): no scramble layer found. It carries no class of its own, ' +\n `so it is addressed structurally as \\`${SCRAMBLE}\\` — a template that moved either span out from ` +\n 'under that selector breaks this read, and \"false\" would report the layer as ANNOUNCED rather than as ' +\n 'missing.'\n );\n }\n\n return (await scramble.getAttribute('aria-hidden')) === 'true';\n }\n\n /**\n * Point at it — the trigger for `animateOn` `'hover'` and `'inViewHover'`.\n *\n * Drives the component the way a user does, and works in a test because the trigger is\n * an event rather than a gesture: `TestElement.hover()` dispatches `pointerenter`,\n * `mouseover` and `mouseenter`, and the component listens for the last of those. A\n * hand-rolled pointer-only dispatch would not reach it.\n *\n * Does nothing in the other two modes, exactly as a real pointer does nothing there.\n */\n async hover(): Promise<void> {\n return (await this.host()).hover();\n }\n\n /**\n * Move the pointer off — the other half of hover mode.\n *\n * Pins the promise that leaving mid-scramble restores the real text IMMEDIATELY rather\n * than letting the reveal play out: the reader took the effect away, so the string has\n * to come back at once.\n */\n async mouseAway(): Promise<void> {\n return (await this.host()).mouseAway();\n }\n\n /**\n * Click the host — the `animateOn=\"click\"` trigger, and the only way to tell\n * `clickMode` `'once'` from `'toggle'`.\n *\n * There is no keyboard equivalent, and that is a statement about the component rather\n * than about this harness: in click mode the host carries a click handler with no role,\n * no `tabindex` and no keydown handler, so a keyboard or switch user cannot reveal the\n * text at all. A `sendKeys(Enter)` here would invent an affordance that does not exist\n * and let a spec pass over the top of it.\n */\n async click(): Promise<void> {\n return (await this.host()).click();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -160,7 +160,12 @@ class WrDecryptText {
|
|
|
160
160
|
const txt = this.text();
|
|
161
161
|
const mode = this.animateOn();
|
|
162
162
|
this.stopInterval();
|
|
163
|
-
|
|
163
|
+
// The resting state of `click` mode is the scramble — but not for someone who
|
|
164
|
+
// asked for less motion. `startInterval` short-circuits every TRIGGER to the
|
|
165
|
+
// plain text, and this is the one path that never reaches it, so a reduced-
|
|
166
|
+
// motion reader was left staring at permanently scrambled glyphs with no
|
|
167
|
+
// pointer-free way to undo them.
|
|
168
|
+
if (mode === 'click' && !this.platform.prefersReducedMotion()) {
|
|
164
169
|
this.encryptInstantly(txt);
|
|
165
170
|
}
|
|
166
171
|
else {
|