@operato/popup 10.11.0 → 10.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +206 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +7985 -0
- package/dist/src/implementation.d.ts +13 -0
- package/dist/src/implementation.js +13 -0
- package/dist/src/implementation.js.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/open-popup.d.ts +37 -13
- package/dist/src/open-popup.js +79 -12
- package/dist/src/open-popup.js.map +1 -1
- package/dist/src/ox-floating-overlay.d.ts +4 -1
- package/dist/src/ox-floating-overlay.js +19 -2
- package/dist/src/ox-floating-overlay.js.map +1 -1
- package/dist/src/ox-popup-actions.d.ts +36 -0
- package/dist/src/ox-popup-actions.js +129 -0
- package/dist/src/ox-popup-actions.js.map +1 -0
- package/dist/src/ox-prompt.d.ts +25 -3
- package/dist/src/ox-prompt.js +74 -42
- package/dist/src/ox-prompt.js.map +1 -1
- package/dist/src/popup-result.d.ts +19 -0
- package/dist/src/popup-result.js +53 -0
- package/dist/src/popup-result.js.map +1 -0
- package/dist/test/office-control-height.test.d.ts +2 -0
- package/dist/test/office-control-height.test.js +102 -0
- package/dist/test/office-control-height.test.js.map +1 -0
- package/dist/test/ox-popup-actions.test.d.ts +3 -0
- package/dist/test/ox-popup-actions.test.js +104 -0
- package/dist/test/ox-popup-actions.test.js.map +1 -0
- package/dist/test/ox-prompt-actions.test.d.ts +1 -0
- package/dist/test/ox-prompt-actions.test.js +108 -0
- package/dist/test/ox-prompt-actions.test.js.map +1 -0
- package/dist/test/popup-result.test.d.ts +1 -0
- package/dist/test/popup-result.test.js +114 -0
- package/dist/test/popup-result.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -5
- package/test/office-control-height.test.ts +125 -0
- package/test/ox-popup-actions.test.ts +130 -0
- package/test/ox-prompt-actions.test.ts +136 -0
- package/test/popup-result.test.ts +150 -0
- package/translations/en.json +6 -1
- package/translations/ja.json +6 -0
- package/translations/ko.json +6 -1
- package/translations/ms.json +6 -0
- package/translations/zh.json +6 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a popup's result waits between "someone asked to close with this value" and "the popup is
|
|
3
|
+
* actually gone".
|
|
4
|
+
*
|
|
5
|
+
* There are two entrances for a value - the opener's `handle.close(value)` and the content's
|
|
6
|
+
* `closePopup(element, value)` - but only one place settles: the overlay leaving the document.
|
|
7
|
+
* Settling at an entrance would give two settling places. So entrances record, the first recorded
|
|
8
|
+
* value wins, and `openPopup` takes it when the implementation reports the popup gone.
|
|
9
|
+
*/
|
|
10
|
+
const pending = new Map();
|
|
11
|
+
/** Called by `openPopup` when a popup opens. Overlays that are not popups never get a slot. */
|
|
12
|
+
export function beginPopupResult(name) {
|
|
13
|
+
pending.set(name, { recorded: false });
|
|
14
|
+
}
|
|
15
|
+
/** Records a close request's value. True when this call recorded it; false when one came first. */
|
|
16
|
+
export function recordPopupResult(name, value) {
|
|
17
|
+
if (!name)
|
|
18
|
+
return false;
|
|
19
|
+
const slot = pending.get(name);
|
|
20
|
+
if (!slot || slot.recorded)
|
|
21
|
+
return false;
|
|
22
|
+
pending.set(name, { recorded: true, value });
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
/** Undoes a record whose request turned out not to close anything, so it cannot become the result. */
|
|
26
|
+
export function forgetPopupResult(name) {
|
|
27
|
+
if (pending.has(name))
|
|
28
|
+
pending.set(name, { recorded: false });
|
|
29
|
+
}
|
|
30
|
+
/** Takes the value once, when the popup is gone. Undefined when it was only dismissed. */
|
|
31
|
+
export function takePopupResult(name) {
|
|
32
|
+
const slot = pending.get(name);
|
|
33
|
+
pending.delete(name);
|
|
34
|
+
return slot?.recorded ? slot.value : undefined;
|
|
35
|
+
}
|
|
36
|
+
/*
|
|
37
|
+
* History-backed overlays close with history.back(). The state it reads to decide ("am I the top
|
|
38
|
+
* overlay?") only changes when popstate arrives, so two close requests in the same tick both saw
|
|
39
|
+
* themselves on top and went back twice - one page past the popup. The first claim goes back; later
|
|
40
|
+
* claims for the same overlay are refused until the next popstate.
|
|
41
|
+
*/
|
|
42
|
+
const goingBack = new Set();
|
|
43
|
+
if (typeof window !== 'undefined') {
|
|
44
|
+
window.addEventListener('popstate', () => goingBack.clear());
|
|
45
|
+
}
|
|
46
|
+
/** True for the first request to go back for this overlay; false while that back is still pending. */
|
|
47
|
+
export function claimHistoryBack(name) {
|
|
48
|
+
if (goingBack.has(name))
|
|
49
|
+
return false;
|
|
50
|
+
goingBack.add(name);
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=popup-result.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popup-result.js","sourceRoot":"","sources":["../../src/popup-result.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAgB,CAAA;AAEvC,+FAA+F;AAC/F,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;AACxC,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,iBAAiB,CAAC,IAAwB,EAAE,KAAc;IACxE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IAEvB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAExC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACb,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;AAC/D,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC9B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAEpB,OAAO,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAChD,CAAC;AAED;;;;;GAKG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAA;AAEnC,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;IAClC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAA;AAC9D,CAAC;AAED,sGAAsG;AACtG,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAErC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACnB,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/**\n * Where a popup's result waits between \"someone asked to close with this value\" and \"the popup is\n * actually gone\".\n *\n * There are two entrances for a value - the opener's `handle.close(value)` and the content's\n * `closePopup(element, value)` - but only one place settles: the overlay leaving the document.\n * Settling at an entrance would give two settling places. So entrances record, the first recorded\n * value wins, and `openPopup` takes it when the implementation reports the popup gone.\n */\n\ntype Slot = { recorded: false } | { recorded: true; value: unknown }\n\nconst pending = new Map<string, Slot>()\n\n/** Called by `openPopup` when a popup opens. Overlays that are not popups never get a slot. */\nexport function beginPopupResult(name: string): void {\n pending.set(name, { recorded: false })\n}\n\n/** Records a close request's value. True when this call recorded it; false when one came first. */\nexport function recordPopupResult(name: string | undefined, value: unknown): boolean {\n if (!name) return false\n\n const slot = pending.get(name)\n if (!slot || slot.recorded) return false\n\n pending.set(name, { recorded: true, value })\n return true\n}\n\n/** Undoes a record whose request turned out not to close anything, so it cannot become the result. */\nexport function forgetPopupResult(name: string): void {\n if (pending.has(name)) pending.set(name, { recorded: false })\n}\n\n/** Takes the value once, when the popup is gone. Undefined when it was only dismissed. */\nexport function takePopupResult(name: string): unknown {\n const slot = pending.get(name)\n pending.delete(name)\n\n return slot?.recorded ? slot.value : undefined\n}\n\n/*\n * History-backed overlays close with history.back(). The state it reads to decide (\"am I the top\n * overlay?\") only changes when popstate arrives, so two close requests in the same tick both saw\n * themselves on top and went back twice - one page past the popup. The first claim goes back; later\n * claims for the same overlay are refused until the next popstate.\n */\nconst goingBack = new Set<string>()\n\nif (typeof window !== 'undefined') {\n window.addEventListener('popstate', () => goingBack.clear())\n}\n\n/** True for the first request to go back for this overlay; false while that back is still pending. */\nexport function claimHistoryBack(name: string): boolean {\n if (goingBack.has(name)) return false\n\n goingBack.add(name)\n return true\n}\n"]}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import '@material/web/button/filled-button.js';
|
|
2
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
3
|
+
import { css, html, LitElement } from 'lit';
|
|
4
|
+
import { ACTION_HEIGHT_TOKENS, ButtonContainerStyles, CommonHeaderStyles, CommonPopupStyles } from '@operato/styles';
|
|
5
|
+
import '../src/ox-popup-actions.js';
|
|
6
|
+
import { OxPopupActions } from '../src/ox-popup-actions.js';
|
|
7
|
+
/**
|
|
8
|
+
* Button heights (ADR-0060 decisions 6 and 6-1).
|
|
9
|
+
*
|
|
10
|
+
* The **platform** keeps the heights every application had before the operato standardization, so
|
|
11
|
+
* dssp, fmsim and the rest do not change when they upgrade. The **operato products** are compact: the
|
|
12
|
+
* shell theme opens each token as `var(--suite-<token>, <platform>)` and operato-suite fills the slot.
|
|
13
|
+
*
|
|
14
|
+
* These render both worlds with a mouse (the test browser). The finger branches cannot be emulated,
|
|
15
|
+
* so those are read from the stylesheets.
|
|
16
|
+
*/
|
|
17
|
+
const PLATFORM_TOKENS = `
|
|
18
|
+
--context-action-height: var(--suite-context-action-height, 32px);
|
|
19
|
+
--popup-action-height: var(--suite-popup-action-height, 30px);
|
|
20
|
+
--form-action-height: var(--suite-form-action-height, 40px);
|
|
21
|
+
`;
|
|
22
|
+
const SUITE_SLOTS = `
|
|
23
|
+
--suite-context-action-height: 32px;
|
|
24
|
+
--suite-popup-action-height: 32px;
|
|
25
|
+
--suite-form-action-height: 32px;
|
|
26
|
+
`;
|
|
27
|
+
class StyledRows extends LitElement {
|
|
28
|
+
static { this.styles = [CommonPopupStyles, CommonHeaderStyles, ButtonContainerStyles, css ``]; }
|
|
29
|
+
render() {
|
|
30
|
+
return html `
|
|
31
|
+
<div class="buttons"><md-filled-button id="popup-md">save</md-filled-button></div>
|
|
32
|
+
<div class="footer"><button id="footer">save</button></div>
|
|
33
|
+
<div class="button-container">
|
|
34
|
+
<button id="bar">export</button>
|
|
35
|
+
<md-filled-button id="bar-md">publish</md-filled-button>
|
|
36
|
+
</div>
|
|
37
|
+
`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
customElements.define('test-styled-rows', StyledRows);
|
|
41
|
+
const height = (el) => Math.round(el.getBoundingClientRect().height);
|
|
42
|
+
const text = (styles) => (Array.isArray(styles) ? styles : [styles]).map(s => String(s.cssText ?? s)).join('\n');
|
|
43
|
+
const coarse = (cssText) => cssText.slice(cssText.indexOf('(pointer: coarse)'));
|
|
44
|
+
async function rows(style) {
|
|
45
|
+
const host = await fixture(html `
|
|
46
|
+
<div style="${style}">
|
|
47
|
+
<test-styled-rows></test-styled-rows>
|
|
48
|
+
<ox-popup-actions>
|
|
49
|
+
<md-filled-button slot="confirm" id="prompt-md">ok</md-filled-button>
|
|
50
|
+
</ox-popup-actions>
|
|
51
|
+
</div>
|
|
52
|
+
`);
|
|
53
|
+
const styled = host.querySelector('test-styled-rows');
|
|
54
|
+
await styled.updateComplete;
|
|
55
|
+
const actions = host.querySelector('ox-popup-actions');
|
|
56
|
+
await actions.updateComplete;
|
|
57
|
+
const buttons = [...Array.from(styled.shadowRoot.querySelectorAll('md-filled-button')), host.querySelector('#prompt-md')];
|
|
58
|
+
await Promise.all(buttons.map(b => b.updateComplete));
|
|
59
|
+
return { root: styled.shadowRoot, host };
|
|
60
|
+
}
|
|
61
|
+
describe('office control height', () => {
|
|
62
|
+
it('the platform defaults are the heights before the standardization', () => {
|
|
63
|
+
expect(ACTION_HEIGHT_TOKENS).to.deep.equal({
|
|
64
|
+
'--context-action-height': 32,
|
|
65
|
+
'--popup-action-height': 30,
|
|
66
|
+
'--form-action-height': 40,
|
|
67
|
+
'--cell-action-height': 26
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it('★ platform: nothing changes for applications outside the operato suite', async () => {
|
|
71
|
+
const { root, host } = await rows(PLATFORM_TOKENS);
|
|
72
|
+
expect(height(root.querySelector('#popup-md'))).to.equal(40); // CommonPopupStyles .buttons
|
|
73
|
+
expect(height(root.querySelector('#footer'))).to.be.at.least(30); // older footer
|
|
74
|
+
expect(height(root.querySelector('#bar'))).to.equal(32); // page action bar
|
|
75
|
+
expect(height(root.querySelector('#bar-md'))).to.equal(40); // Material keeps its own in the bar
|
|
76
|
+
expect(height(host.querySelector('#prompt-md'))).to.equal(30); // OxPrompt row, as before
|
|
77
|
+
});
|
|
78
|
+
it('★ suite: every action button in the operato products is 32px', async () => {
|
|
79
|
+
const { root, host } = await rows(SUITE_SLOTS + PLATFORM_TOKENS);
|
|
80
|
+
expect(height(root.querySelector('#popup-md'))).to.equal(32);
|
|
81
|
+
expect(height(root.querySelector('#bar'))).to.equal(32);
|
|
82
|
+
expect(height(root.querySelector('#bar-md'))).to.equal(32);
|
|
83
|
+
expect(height(host.querySelector('#prompt-md'))).to.equal(32);
|
|
84
|
+
});
|
|
85
|
+
it('a shop-floor screen still raises the popup row above both', async () => {
|
|
86
|
+
const { root } = await rows(SUITE_SLOTS + PLATFORM_TOKENS + '--popup-action-height: 64px;');
|
|
87
|
+
expect(height(root.querySelector('#popup-md'))).to.equal(64);
|
|
88
|
+
});
|
|
89
|
+
it('finger: the platform keeps 44px for the page bar and a 48px floor for the popup row', () => {
|
|
90
|
+
const bar = coarse(text(ButtonContainerStyles));
|
|
91
|
+
expect(bar).to.contain('var(--suite-context-action-height, 44px)');
|
|
92
|
+
const popup = coarse(text(CommonPopupStyles));
|
|
93
|
+
expect(popup).to.contain('var(--suite-popup-action-height, 48px)');
|
|
94
|
+
expect(popup).to.contain('var(--popup-action-height, 30px)');
|
|
95
|
+
});
|
|
96
|
+
it('ox-popup-actions has no finger height of its own', () => {
|
|
97
|
+
const cssText = text(OxPopupActions.styles);
|
|
98
|
+
expect(cssText).to.contain('--_height: var(--popup-action-height, 30px)');
|
|
99
|
+
expect(coarse(cssText)).to.not.contain('--_height:');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
//# sourceMappingURL=office-control-height.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"office-control-height.test.js","sourceRoot":"","sources":["../../test/office-control-height.test.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\n\nimport { expect, fixture } from '@open-wc/testing'\nimport { css, html, LitElement } from 'lit'\n\nimport {\n ACTION_HEIGHT_TOKENS,\n ButtonContainerStyles,\n CommonHeaderStyles,\n CommonPopupStyles\n} from '@operato/styles'\n\nimport '../src/ox-popup-actions.js'\nimport { OxPopupActions } from '../src/ox-popup-actions.js'\n\n/**\n * Button heights (ADR-0060 decisions 6 and 6-1).\n *\n * The **platform** keeps the heights every application had before the operato standardization, so\n * dssp, fmsim and the rest do not change when they upgrade. The **operato products** are compact: the\n * shell theme opens each token as `var(--suite-<token>, <platform>)` and operato-suite fills the slot.\n *\n * These render both worlds with a mouse (the test browser). The finger branches cannot be emulated,\n * so those are read from the stylesheets.\n */\n\nconst PLATFORM_TOKENS = `\n --context-action-height: var(--suite-context-action-height, 32px);\n --popup-action-height: var(--suite-popup-action-height, 30px);\n --form-action-height: var(--suite-form-action-height, 40px);\n`\nconst SUITE_SLOTS = `\n --suite-context-action-height: 32px;\n --suite-popup-action-height: 32px;\n --suite-form-action-height: 32px;\n`\n\nclass StyledRows extends LitElement {\n static styles = [CommonPopupStyles, CommonHeaderStyles, ButtonContainerStyles, css``]\n\n render() {\n return html`\n <div class=\"buttons\"><md-filled-button id=\"popup-md\">save</md-filled-button></div>\n <div class=\"footer\"><button id=\"footer\">save</button></div>\n <div class=\"button-container\">\n <button id=\"bar\">export</button>\n <md-filled-button id=\"bar-md\">publish</md-filled-button>\n </div>\n `\n }\n}\ncustomElements.define('test-styled-rows', StyledRows)\n\nconst height = (el: Element | null | undefined) => Math.round(el!.getBoundingClientRect().height)\nconst text = (styles: unknown) =>\n (Array.isArray(styles) ? styles : [styles]).map(s => String((s as { cssText?: string }).cssText ?? s)).join('\\n')\nconst coarse = (cssText: string) => cssText.slice(cssText.indexOf('(pointer: coarse)'))\n\nasync function rows(style: string) {\n const host = await fixture<HTMLElement>(html`\n <div style=\"${style}\">\n <test-styled-rows></test-styled-rows>\n <ox-popup-actions>\n <md-filled-button slot=\"confirm\" id=\"prompt-md\">ok</md-filled-button>\n </ox-popup-actions>\n </div>\n `)\n const styled = host.querySelector('test-styled-rows') as StyledRows\n await styled.updateComplete\n const actions = host.querySelector('ox-popup-actions') as OxPopupActions\n await actions.updateComplete\n const buttons = [...Array.from(styled.shadowRoot!.querySelectorAll('md-filled-button')), host.querySelector('#prompt-md')]\n await Promise.all(buttons.map(b => (b as LitElement).updateComplete))\n return { root: styled.shadowRoot!, host }\n}\n\ndescribe('office control height', () => {\n it('the platform defaults are the heights before the standardization', () => {\n expect(ACTION_HEIGHT_TOKENS).to.deep.equal({\n '--context-action-height': 32,\n '--popup-action-height': 30,\n '--form-action-height': 40,\n '--cell-action-height': 26\n })\n })\n\n it('★ platform: nothing changes for applications outside the operato suite', async () => {\n const { root, host } = await rows(PLATFORM_TOKENS)\n\n expect(height(root.querySelector('#popup-md'))).to.equal(40) // CommonPopupStyles .buttons\n expect(height(root.querySelector('#footer'))).to.be.at.least(30) // older footer\n expect(height(root.querySelector('#bar'))).to.equal(32) // page action bar\n expect(height(root.querySelector('#bar-md'))).to.equal(40) // Material keeps its own in the bar\n expect(height(host.querySelector('#prompt-md'))).to.equal(30) // OxPrompt row, as before\n })\n\n it('★ suite: every action button in the operato products is 32px', async () => {\n const { root, host } = await rows(SUITE_SLOTS + PLATFORM_TOKENS)\n\n expect(height(root.querySelector('#popup-md'))).to.equal(32)\n expect(height(root.querySelector('#bar'))).to.equal(32)\n expect(height(root.querySelector('#bar-md'))).to.equal(32)\n expect(height(host.querySelector('#prompt-md'))).to.equal(32)\n })\n\n it('a shop-floor screen still raises the popup row above both', async () => {\n const { root } = await rows(SUITE_SLOTS + PLATFORM_TOKENS + '--popup-action-height: 64px;')\n expect(height(root.querySelector('#popup-md'))).to.equal(64)\n })\n\n it('finger: the platform keeps 44px for the page bar and a 48px floor for the popup row', () => {\n const bar = coarse(text(ButtonContainerStyles))\n expect(bar).to.contain('var(--suite-context-action-height, 44px)')\n\n const popup = coarse(text(CommonPopupStyles))\n expect(popup).to.contain('var(--suite-popup-action-height, 48px)')\n expect(popup).to.contain('var(--popup-action-height, 30px)')\n })\n\n it('ox-popup-actions has no finger height of its own', () => {\n const cssText = text(OxPopupActions.styles)\n expect(cssText).to.contain('--_height: var(--popup-action-height, 30px)')\n expect(coarse(cssText)).to.not.contain('--_height:')\n })\n})\n"]}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import '@material/web/button/filled-button.js';
|
|
2
|
+
import '@material/web/button/text-button.js';
|
|
3
|
+
import { expect, fixture } from '@open-wc/testing';
|
|
4
|
+
import { html } from 'lit';
|
|
5
|
+
import '../src/ox-popup-actions.js';
|
|
6
|
+
/**
|
|
7
|
+
* The action row owns order, height, shape and the destructive colour - so a popup cannot get them
|
|
8
|
+
* wrong by writing its markup differently. These run with a mouse pointer (the test browser); the
|
|
9
|
+
* finger bar is a media query of the same element.
|
|
10
|
+
*
|
|
11
|
+
* The expected numbers are written out (30, 68, 999em) rather than read from the stylesheet, so a
|
|
12
|
+
* change to the stylesheet shows up here as a decision, not as a silent follow.
|
|
13
|
+
*/
|
|
14
|
+
const left = (el) => el.getBoundingClientRect().left;
|
|
15
|
+
const height = (el) => Math.round(el.getBoundingClientRect().height);
|
|
16
|
+
async function row(template) {
|
|
17
|
+
const host = await fixture(html `<div style="width: 600px">${template}</div>`);
|
|
18
|
+
const actions = host.querySelector('ox-popup-actions');
|
|
19
|
+
await actions.updateComplete;
|
|
20
|
+
await Promise.all(Array.from(actions.querySelectorAll('md-filled-button, md-text-button')).map(b => b.updateComplete));
|
|
21
|
+
return actions;
|
|
22
|
+
}
|
|
23
|
+
describe('ox-popup-actions', () => {
|
|
24
|
+
it('renders start, cancel, confirm in that order whatever the markup order', async () => {
|
|
25
|
+
const actions = await row(html `
|
|
26
|
+
<ox-popup-actions>
|
|
27
|
+
<md-filled-button slot="confirm" id="confirm">save</md-filled-button>
|
|
28
|
+
<md-text-button slot="cancel" id="cancel">cancel</md-text-button>
|
|
29
|
+
<md-text-button slot="start" id="start">delete</md-text-button>
|
|
30
|
+
</ox-popup-actions>
|
|
31
|
+
`);
|
|
32
|
+
const start = actions.querySelector('#start');
|
|
33
|
+
const cancel = actions.querySelector('#cancel');
|
|
34
|
+
const confirm = actions.querySelector('#confirm');
|
|
35
|
+
expect(left(start)).to.be.lessThan(left(cancel));
|
|
36
|
+
expect(left(cancel)).to.be.lessThan(left(confirm));
|
|
37
|
+
});
|
|
38
|
+
it('keeps the confirm button at the right edge', async () => {
|
|
39
|
+
const actions = await row(html `
|
|
40
|
+
<ox-popup-actions>
|
|
41
|
+
<md-filled-button slot="confirm" id="confirm">save</md-filled-button>
|
|
42
|
+
<md-text-button slot="cancel">cancel</md-text-button>
|
|
43
|
+
</ox-popup-actions>
|
|
44
|
+
`);
|
|
45
|
+
const confirm = actions.querySelector('#confirm');
|
|
46
|
+
expect(Math.round(confirm.getBoundingClientRect().right)).to.equal(Math.round(actions.getBoundingClientRect().right));
|
|
47
|
+
});
|
|
48
|
+
it('gives Material buttons and plain buttons the same height (30px, the platform default)', async () => {
|
|
49
|
+
const actions = await row(html `
|
|
50
|
+
<ox-popup-actions>
|
|
51
|
+
<button slot="cancel" id="plain">cancel</button>
|
|
52
|
+
<md-filled-button slot="confirm" id="md">save</md-filled-button>
|
|
53
|
+
</ox-popup-actions>
|
|
54
|
+
`);
|
|
55
|
+
expect(height(actions.querySelector('#md'))).to.equal(30);
|
|
56
|
+
expect(height(actions.querySelector('#plain'))).to.equal(30);
|
|
57
|
+
});
|
|
58
|
+
it('uses pill shapes inside the popup card', async () => {
|
|
59
|
+
const actions = await row(html `
|
|
60
|
+
<ox-popup-actions>
|
|
61
|
+
<button slot="cancel" id="plain">cancel</button>
|
|
62
|
+
<md-filled-button slot="confirm" id="md">save</md-filled-button>
|
|
63
|
+
</ox-popup-actions>
|
|
64
|
+
`);
|
|
65
|
+
const md = actions.querySelector('#md');
|
|
66
|
+
expect(getComputedStyle(md).getPropertyValue('--md-filled-button-container-shape').trim()).to.equal('999em');
|
|
67
|
+
expect(getComputedStyle(actions.querySelector('#plain')).borderTopLeftRadius).to.not.equal('0px');
|
|
68
|
+
});
|
|
69
|
+
it('lets a screen raise the height through --popup-action-height', async () => {
|
|
70
|
+
const actions = await row(html `
|
|
71
|
+
<ox-popup-actions style="--popup-action-height: 68px">
|
|
72
|
+
<button slot="cancel" id="plain">cancel</button>
|
|
73
|
+
<md-filled-button slot="confirm" id="md">save</md-filled-button>
|
|
74
|
+
</ox-popup-actions>
|
|
75
|
+
`);
|
|
76
|
+
expect(height(actions.querySelector('#md'))).to.equal(68);
|
|
77
|
+
expect(height(actions.querySelector('#plain'))).to.equal(68);
|
|
78
|
+
});
|
|
79
|
+
it('paints a destructive action with the error colour', async () => {
|
|
80
|
+
const actions = await row(html `
|
|
81
|
+
<ox-popup-actions style="--md-sys-color-error: rgb(190, 12, 20)">
|
|
82
|
+
<md-text-button slot="start" destructive id="side">delete</md-text-button>
|
|
83
|
+
<md-filled-button slot="confirm" destructive id="main">delete all</md-filled-button>
|
|
84
|
+
<button slot="cancel" id="plain" destructive>cancel</button>
|
|
85
|
+
</ox-popup-actions>
|
|
86
|
+
`);
|
|
87
|
+
const side = getComputedStyle(actions.querySelector('#side'));
|
|
88
|
+
const main = getComputedStyle(actions.querySelector('#main'));
|
|
89
|
+
expect(side.getPropertyValue('--md-text-button-label-text-color').trim()).to.equal('rgb(190, 12, 20)');
|
|
90
|
+
expect(main.getPropertyValue('--md-filled-button-container-color').trim()).to.equal('rgb(190, 12, 20)');
|
|
91
|
+
});
|
|
92
|
+
it('never paints cancel as destructive', async () => {
|
|
93
|
+
const actions = await row(html `
|
|
94
|
+
<ox-popup-actions style="--md-sys-color-error: rgb(190, 12, 20)">
|
|
95
|
+
<md-text-button slot="cancel" destructive id="cancel">cancel</md-text-button>
|
|
96
|
+
<button slot="cancel" destructive id="plain">cancel</button>
|
|
97
|
+
<md-filled-button slot="confirm">save</md-filled-button>
|
|
98
|
+
</ox-popup-actions>
|
|
99
|
+
`);
|
|
100
|
+
expect(getComputedStyle(actions.querySelector('#cancel')).getPropertyValue('--md-text-button-label-text-color').trim()).to.not.equal('rgb(190, 12, 20)');
|
|
101
|
+
expect(getComputedStyle(actions.querySelector('#plain')).color).to.not.equal('rgb(190, 12, 20)');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=ox-popup-actions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ox-popup-actions.test.js","sourceRoot":"","sources":["../../test/ox-popup-actions.test.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/button/filled-button.js'\nimport '@material/web/button/text-button.js'\n\nimport { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport '../src/ox-popup-actions.js'\n\n/**\n * The action row owns order, height, shape and the destructive colour - so a popup cannot get them\n * wrong by writing its markup differently. These run with a mouse pointer (the test browser); the\n * finger bar is a media query of the same element.\n *\n * The expected numbers are written out (30, 68, 999em) rather than read from the stylesheet, so a\n * change to the stylesheet shows up here as a decision, not as a silent follow.\n */\n\nconst left = (el: Element) => el.getBoundingClientRect().left\nconst height = (el: Element) => Math.round(el.getBoundingClientRect().height)\n\nasync function row(template: ReturnType<typeof html>) {\n const host = await fixture<HTMLElement>(html`<div style=\"width: 600px\">${template}</div>`)\n const actions = host.querySelector('ox-popup-actions') as HTMLElement & { updateComplete: Promise<unknown> }\n await actions.updateComplete\n await Promise.all(\n Array.from(actions.querySelectorAll('md-filled-button, md-text-button')).map(\n b => (b as HTMLElement & { updateComplete: Promise<unknown> }).updateComplete\n )\n )\n return actions\n}\n\ndescribe('ox-popup-actions', () => {\n it('renders start, cancel, confirm in that order whatever the markup order', async () => {\n const actions = await row(html`\n <ox-popup-actions>\n <md-filled-button slot=\"confirm\" id=\"confirm\">save</md-filled-button>\n <md-text-button slot=\"cancel\" id=\"cancel\">cancel</md-text-button>\n <md-text-button slot=\"start\" id=\"start\">delete</md-text-button>\n </ox-popup-actions>\n `)\n\n const start = actions.querySelector('#start')!\n const cancel = actions.querySelector('#cancel')!\n const confirm = actions.querySelector('#confirm')!\n\n expect(left(start)).to.be.lessThan(left(cancel))\n expect(left(cancel)).to.be.lessThan(left(confirm))\n })\n\n it('keeps the confirm button at the right edge', async () => {\n const actions = await row(html`\n <ox-popup-actions>\n <md-filled-button slot=\"confirm\" id=\"confirm\">save</md-filled-button>\n <md-text-button slot=\"cancel\">cancel</md-text-button>\n </ox-popup-actions>\n `)\n\n const confirm = actions.querySelector('#confirm')!\n expect(Math.round(confirm.getBoundingClientRect().right)).to.equal(Math.round(actions.getBoundingClientRect().right))\n })\n\n it('gives Material buttons and plain buttons the same height (30px, the platform default)', async () => {\n const actions = await row(html`\n <ox-popup-actions>\n <button slot=\"cancel\" id=\"plain\">cancel</button>\n <md-filled-button slot=\"confirm\" id=\"md\">save</md-filled-button>\n </ox-popup-actions>\n `)\n\n expect(height(actions.querySelector('#md')!)).to.equal(30)\n expect(height(actions.querySelector('#plain')!)).to.equal(30)\n })\n\n it('uses pill shapes inside the popup card', async () => {\n const actions = await row(html`\n <ox-popup-actions>\n <button slot=\"cancel\" id=\"plain\">cancel</button>\n <md-filled-button slot=\"confirm\" id=\"md\">save</md-filled-button>\n </ox-popup-actions>\n `)\n\n const md = actions.querySelector('#md')!\n expect(getComputedStyle(md).getPropertyValue('--md-filled-button-container-shape').trim()).to.equal('999em')\n expect(getComputedStyle(actions.querySelector('#plain')!).borderTopLeftRadius).to.not.equal('0px')\n })\n\n it('lets a screen raise the height through --popup-action-height', async () => {\n const actions = await row(html`\n <ox-popup-actions style=\"--popup-action-height: 68px\">\n <button slot=\"cancel\" id=\"plain\">cancel</button>\n <md-filled-button slot=\"confirm\" id=\"md\">save</md-filled-button>\n </ox-popup-actions>\n `)\n\n expect(height(actions.querySelector('#md')!)).to.equal(68)\n expect(height(actions.querySelector('#plain')!)).to.equal(68)\n })\n\n it('paints a destructive action with the error colour', async () => {\n const actions = await row(html`\n <ox-popup-actions style=\"--md-sys-color-error: rgb(190, 12, 20)\">\n <md-text-button slot=\"start\" destructive id=\"side\">delete</md-text-button>\n <md-filled-button slot=\"confirm\" destructive id=\"main\">delete all</md-filled-button>\n <button slot=\"cancel\" id=\"plain\" destructive>cancel</button>\n </ox-popup-actions>\n `)\n\n const side = getComputedStyle(actions.querySelector('#side')!)\n const main = getComputedStyle(actions.querySelector('#main')!)\n\n expect(side.getPropertyValue('--md-text-button-label-text-color').trim()).to.equal('rgb(190, 12, 20)')\n expect(main.getPropertyValue('--md-filled-button-container-color').trim()).to.equal('rgb(190, 12, 20)')\n })\n\n it('never paints cancel as destructive', async () => {\n const actions = await row(html`\n <ox-popup-actions style=\"--md-sys-color-error: rgb(190, 12, 20)\">\n <md-text-button slot=\"cancel\" destructive id=\"cancel\">cancel</md-text-button>\n <button slot=\"cancel\" destructive id=\"plain\">cancel</button>\n <md-filled-button slot=\"confirm\">save</md-filled-button>\n </ox-popup-actions>\n `)\n\n expect(getComputedStyle(actions.querySelector('#cancel')!).getPropertyValue('--md-text-button-label-text-color').trim()).to.not.equal(\n 'rgb(190, 12, 20)'\n )\n expect(getComputedStyle(actions.querySelector('#plain')!).color).to.not.equal('rgb(190, 12, 20)')\n })\n})\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { OxPrompt } from '../src/ox-prompt.js';
|
|
3
|
+
/**
|
|
4
|
+
* OxPrompt is the first real consumer of the shared action row. Before, its buttons were centred,
|
|
5
|
+
* confirm stood left of cancel, and both had the theme's 5px corner inside a floating card - a row of
|
|
6
|
+
* its own, unlike every popup that opened it.
|
|
7
|
+
*
|
|
8
|
+
* Checked here: the row is the shared one, confirm is on the right, heights match, a destructive
|
|
9
|
+
* confirmation is painted and focuses cancel, and the promise still answers true / false.
|
|
10
|
+
*/
|
|
11
|
+
const settle = () => new Promise(resolve => setTimeout(resolve, 50));
|
|
12
|
+
const TIMEOUT = 'TIMEOUT';
|
|
13
|
+
function within(promise, ms = 1500) {
|
|
14
|
+
return Promise.race([promise, new Promise(resolve => setTimeout(() => resolve(TIMEOUT), ms))]);
|
|
15
|
+
}
|
|
16
|
+
async function openPrompt(destructive = false) {
|
|
17
|
+
const answer = OxPrompt.open({
|
|
18
|
+
title: 'remove?',
|
|
19
|
+
text: 'this cannot be undone',
|
|
20
|
+
confirmButton: { text: 'remove', destructive },
|
|
21
|
+
cancelButton: { text: 'cancel' },
|
|
22
|
+
preventCloseOnBlur: true
|
|
23
|
+
});
|
|
24
|
+
await settle();
|
|
25
|
+
const prompts = document.body.querySelectorAll('ox-prompt');
|
|
26
|
+
const prompt = prompts[prompts.length - 1];
|
|
27
|
+
await prompt.updateComplete;
|
|
28
|
+
const root = prompt.renderRoot;
|
|
29
|
+
const actions = root.querySelector('ox-popup-actions');
|
|
30
|
+
await actions?.updateComplete;
|
|
31
|
+
const confirm = root.querySelector('#confirm');
|
|
32
|
+
const cancel = root.querySelector('#cancel');
|
|
33
|
+
await Promise.all([confirm?.updateComplete, cancel?.updateComplete]);
|
|
34
|
+
return { answer, prompt, root, actions, confirm, cancel };
|
|
35
|
+
}
|
|
36
|
+
describe('OxPrompt uses the shared action row', () => {
|
|
37
|
+
before(() => {
|
|
38
|
+
/* No theme is loaded in the test page; an undefined var() would make every colour read empty. */
|
|
39
|
+
document.body.style.setProperty('--md-sys-color-error', 'rgb(190, 12, 20)');
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
/* A failed assertion before the click leaves its prompt open; do not let it leak into the next test. */
|
|
43
|
+
document.body.querySelectorAll('ox-prompt').forEach(p => p.close());
|
|
44
|
+
});
|
|
45
|
+
it('renders its buttons in ox-popup-actions, confirm right of cancel, same height', async () => {
|
|
46
|
+
const { answer, actions, confirm, cancel } = await openPrompt();
|
|
47
|
+
expect(actions, 'ox-popup-actions').to.exist;
|
|
48
|
+
expect(confirm.getAttribute('slot')).to.equal('confirm');
|
|
49
|
+
expect(cancel.getAttribute('slot')).to.equal('cancel');
|
|
50
|
+
expect(cancel.getBoundingClientRect().left).to.be.lessThan(confirm.getBoundingClientRect().left);
|
|
51
|
+
expect(Math.round(confirm.getBoundingClientRect().height)).to.equal(30);
|
|
52
|
+
expect(Math.round(cancel.getBoundingClientRect().height)).to.equal(30);
|
|
53
|
+
cancel.click();
|
|
54
|
+
expect(await within(answer)).to.equal(false);
|
|
55
|
+
});
|
|
56
|
+
it('answers true on confirm', async () => {
|
|
57
|
+
const { answer, confirm } = await openPrompt();
|
|
58
|
+
confirm.click();
|
|
59
|
+
expect(await within(answer)).to.equal(true);
|
|
60
|
+
});
|
|
61
|
+
it('focuses confirm for an ordinary question', async () => {
|
|
62
|
+
const { answer, root, confirm, cancel } = await openPrompt();
|
|
63
|
+
/* Compare ids, not elements: a failing element comparison makes chai render the DOM and hang. */
|
|
64
|
+
expect(root.activeElement?.id).to.equal('confirm');
|
|
65
|
+
cancel.click();
|
|
66
|
+
await within(answer);
|
|
67
|
+
});
|
|
68
|
+
it('paints a destructive confirmation and focuses cancel', async () => {
|
|
69
|
+
const { answer, root, confirm, cancel } = await openPrompt(true);
|
|
70
|
+
expect(confirm.hasAttribute('destructive')).to.equal(true);
|
|
71
|
+
expect(getComputedStyle(confirm).getPropertyValue('--md-filled-button-container-color').trim()).to.equal('rgb(190, 12, 20)');
|
|
72
|
+
expect(root.activeElement?.id).to.equal('cancel');
|
|
73
|
+
cancel.click();
|
|
74
|
+
expect(await within(answer)).to.equal(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('OxPrompt.confirmDelete', () => {
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
document.body.querySelectorAll('ox-prompt').forEach(p => p.close());
|
|
80
|
+
});
|
|
81
|
+
async function opened(count) {
|
|
82
|
+
const before = document.body.querySelectorAll('ox-prompt').length;
|
|
83
|
+
const answer = OxPrompt.confirmDelete({ count });
|
|
84
|
+
for (let i = 0; i < 40 && document.body.querySelectorAll('ox-prompt').length <= before; i++)
|
|
85
|
+
await settle();
|
|
86
|
+
const prompts = document.body.querySelectorAll('ox-prompt');
|
|
87
|
+
const prompt = prompts[prompts.length - 1];
|
|
88
|
+
await prompt.updateComplete;
|
|
89
|
+
const root = prompt.renderRoot;
|
|
90
|
+
const confirm = root.querySelector('#confirm');
|
|
91
|
+
await confirm?.updateComplete;
|
|
92
|
+
return { answer, prompt, root, confirm };
|
|
93
|
+
}
|
|
94
|
+
it('asks in the shared popup with a destructive delete, and answers true on delete', async () => {
|
|
95
|
+
const { answer, prompt, confirm } = await opened();
|
|
96
|
+
expect(prompt.titler).to.equal('delete the selected item?');
|
|
97
|
+
expect(confirm.hasAttribute('destructive')).to.equal(true);
|
|
98
|
+
expect(confirm.textContent?.trim()).to.equal('delete');
|
|
99
|
+
confirm.click();
|
|
100
|
+
expect(await within(answer)).to.equal(true);
|
|
101
|
+
});
|
|
102
|
+
it('names how many are selected when more than one', async () => {
|
|
103
|
+
const { prompt, root } = await opened(3);
|
|
104
|
+
expect(prompt.titler).to.equal('delete the 3 selected items?');
|
|
105
|
+
root.querySelector('#cancel').click();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=ox-prompt-actions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ox-prompt-actions.test.js","sourceRoot":"","sources":["../../test/ox-prompt-actions.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\n\nimport { OxPrompt } from '../src/ox-prompt.js'\n\n/**\n * OxPrompt is the first real consumer of the shared action row. Before, its buttons were centred,\n * confirm stood left of cancel, and both had the theme's 5px corner inside a floating card - a row of\n * its own, unlike every popup that opened it.\n *\n * Checked here: the row is the shared one, confirm is on the right, heights match, a destructive\n * confirmation is painted and focuses cancel, and the promise still answers true / false.\n */\n\nconst settle = () => new Promise(resolve => setTimeout(resolve, 50))\n\nconst TIMEOUT = 'TIMEOUT'\nfunction within<T>(promise: Promise<T>, ms = 1500): Promise<T | typeof TIMEOUT> {\n return Promise.race([promise, new Promise<typeof TIMEOUT>(resolve => setTimeout(() => resolve(TIMEOUT), ms))])\n}\n\nasync function openPrompt(destructive = false) {\n const answer = OxPrompt.open({\n title: 'remove?',\n text: 'this cannot be undone',\n confirmButton: { text: 'remove', destructive },\n cancelButton: { text: 'cancel' },\n preventCloseOnBlur: true\n })\n\n await settle()\n\n const prompts = document.body.querySelectorAll('ox-prompt')\n const prompt = prompts[prompts.length - 1] as OxPrompt\n await prompt.updateComplete\n\n const root = prompt.renderRoot as ShadowRoot\n const actions = root.querySelector('ox-popup-actions') as HTMLElement & { updateComplete: Promise<unknown> }\n await actions?.updateComplete\n\n const confirm = root.querySelector('#confirm') as HTMLElement & { updateComplete: Promise<unknown> }\n const cancel = root.querySelector('#cancel') as HTMLElement & { updateComplete: Promise<unknown> }\n await Promise.all([confirm?.updateComplete, cancel?.updateComplete])\n\n return { answer, prompt, root, actions, confirm, cancel }\n}\n\ndescribe('OxPrompt uses the shared action row', () => {\n before(() => {\n /* No theme is loaded in the test page; an undefined var() would make every colour read empty. */\n document.body.style.setProperty('--md-sys-color-error', 'rgb(190, 12, 20)')\n })\n\n afterEach(() => {\n /* A failed assertion before the click leaves its prompt open; do not let it leak into the next test. */\n document.body.querySelectorAll('ox-prompt').forEach(p => (p as OxPrompt).close())\n })\n\n it('renders its buttons in ox-popup-actions, confirm right of cancel, same height', async () => {\n const { answer, actions, confirm, cancel } = await openPrompt()\n\n expect(actions, 'ox-popup-actions').to.exist\n expect(confirm.getAttribute('slot')).to.equal('confirm')\n expect(cancel.getAttribute('slot')).to.equal('cancel')\n expect(cancel.getBoundingClientRect().left).to.be.lessThan(confirm.getBoundingClientRect().left)\n expect(Math.round(confirm.getBoundingClientRect().height)).to.equal(30)\n expect(Math.round(cancel.getBoundingClientRect().height)).to.equal(30)\n\n cancel.click()\n expect(await within(answer)).to.equal(false)\n })\n\n it('answers true on confirm', async () => {\n const { answer, confirm } = await openPrompt()\n\n confirm.click()\n expect(await within(answer)).to.equal(true)\n })\n\n it('focuses confirm for an ordinary question', async () => {\n const { answer, root, confirm, cancel } = await openPrompt()\n\n /* Compare ids, not elements: a failing element comparison makes chai render the DOM and hang. */\n expect((root.activeElement as HTMLElement | null)?.id).to.equal('confirm')\n\n cancel.click()\n await within(answer)\n })\n\n it('paints a destructive confirmation and focuses cancel', async () => {\n const { answer, root, confirm, cancel } = await openPrompt(true)\n\n expect(confirm.hasAttribute('destructive')).to.equal(true)\n expect(getComputedStyle(confirm).getPropertyValue('--md-filled-button-container-color').trim()).to.equal('rgb(190, 12, 20)')\n expect((root.activeElement as HTMLElement | null)?.id).to.equal('cancel')\n\n cancel.click()\n expect(await within(answer)).to.equal(false)\n })\n})\n\ndescribe('OxPrompt.confirmDelete', () => {\n afterEach(() => {\n document.body.querySelectorAll('ox-prompt').forEach(p => (p as OxPrompt).close())\n })\n\n async function opened(count?: number) {\n const before = document.body.querySelectorAll('ox-prompt').length\n const answer = OxPrompt.confirmDelete({ count })\n for (let i = 0; i < 40 && document.body.querySelectorAll('ox-prompt').length <= before; i++) await settle()\n const prompts = document.body.querySelectorAll('ox-prompt')\n const prompt = prompts[prompts.length - 1] as OxPrompt\n await prompt.updateComplete\n const root = prompt.renderRoot as ShadowRoot\n const confirm = root.querySelector('#confirm') as HTMLElement & { updateComplete: Promise<unknown> }\n await confirm?.updateComplete\n return { answer, prompt, root, confirm }\n }\n\n it('asks in the shared popup with a destructive delete, and answers true on delete', async () => {\n const { answer, prompt, confirm } = await opened()\n\n expect(prompt.titler).to.equal('delete the selected item?')\n expect(confirm.hasAttribute('destructive')).to.equal(true)\n expect(confirm.textContent?.trim()).to.equal('delete')\n\n confirm.click()\n expect(await within(answer)).to.equal(true)\n })\n\n it('names how many are selected when more than one', async () => {\n const { prompt, root } = await opened(3)\n\n expect(prompt.titler).to.equal('delete the 3 selected items?')\n ;(root.querySelector('#cancel') as HTMLElement).click()\n })\n})\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect } from '@open-wc/testing';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
import { closePopup, openPopup } from '../src/open-popup.js';
|
|
4
|
+
/**
|
|
5
|
+
* The popup result settles exactly once, with the right value, whichever way the popup closes -
|
|
6
|
+
* for the default implementation (no @operato/layout loaded).
|
|
7
|
+
*
|
|
8
|
+
* The implementation's one promise is "call onclosed once when the popup is gone". Before this test
|
|
9
|
+
* the default implementation kept it only for handle.close(): ESC, the backdrop and X removed the
|
|
10
|
+
* overlay without telling the handle, so an awaited result would have hung forever.
|
|
11
|
+
*
|
|
12
|
+
* The same table runs for the layout implementation in @operato/layout/test. A new implementation
|
|
13
|
+
* adds its own row there.
|
|
14
|
+
*/
|
|
15
|
+
const TIMEOUT = 'TIMEOUT';
|
|
16
|
+
/** Never await a result bare - a broken implementation must fail the test, not hang the runner. */
|
|
17
|
+
function within(promise, ms = 2000) {
|
|
18
|
+
return Promise.race([promise, new Promise(resolve => setTimeout(() => resolve(TIMEOUT), ms))]);
|
|
19
|
+
}
|
|
20
|
+
function overlayOf(handle) {
|
|
21
|
+
return document.getElementById(handle.name);
|
|
22
|
+
}
|
|
23
|
+
async function open(options = {}) {
|
|
24
|
+
const handle = openPopup(html `<div class="content">content</div>`, { backdrop: true, ...options });
|
|
25
|
+
let onclosedCalls = 0;
|
|
26
|
+
handle.onclosed = () => onclosedCalls++;
|
|
27
|
+
const overlay = overlayOf(handle);
|
|
28
|
+
await overlay.updateComplete;
|
|
29
|
+
return {
|
|
30
|
+
handle,
|
|
31
|
+
overlay,
|
|
32
|
+
content: overlay.querySelector('.content'),
|
|
33
|
+
onclosedCalls: () => onclosedCalls
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** Lets any late second settle or second onclosed show up before counting. */
|
|
37
|
+
const settleTurn = () => new Promise(resolve => setTimeout(resolve, 50));
|
|
38
|
+
describe('openPopup result - default implementation', () => {
|
|
39
|
+
it('handle.close(value) settles with that value', async () => {
|
|
40
|
+
const { handle, onclosedCalls } = await open();
|
|
41
|
+
handle.close('saved');
|
|
42
|
+
expect(await within(handle.result)).to.equal('saved');
|
|
43
|
+
await settleTurn();
|
|
44
|
+
expect(handle.closed).to.equal(true);
|
|
45
|
+
expect(onclosedCalls()).to.equal(1);
|
|
46
|
+
});
|
|
47
|
+
it('closePopup(element, value) from the content settles with that value', async () => {
|
|
48
|
+
const { handle, content, onclosedCalls } = await open();
|
|
49
|
+
closePopup(content, 'picked');
|
|
50
|
+
expect(await within(handle.result)).to.equal('picked');
|
|
51
|
+
await settleTurn();
|
|
52
|
+
expect(handle.closed).to.equal(true);
|
|
53
|
+
expect(onclosedCalls()).to.equal(1);
|
|
54
|
+
});
|
|
55
|
+
it('closePopup(element) without a value settles undefined', async () => {
|
|
56
|
+
const { handle, content } = await open();
|
|
57
|
+
closePopup(content);
|
|
58
|
+
expect(await within(handle.result)).to.equal(undefined);
|
|
59
|
+
});
|
|
60
|
+
it('ESC settles undefined and reports closed', async () => {
|
|
61
|
+
const { handle, onclosedCalls } = await open({ escapable: true });
|
|
62
|
+
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
|
|
63
|
+
expect(await within(handle.result)).to.equal(undefined);
|
|
64
|
+
await settleTurn();
|
|
65
|
+
expect(handle.closed).to.equal(true);
|
|
66
|
+
expect(onclosedCalls()).to.equal(1);
|
|
67
|
+
});
|
|
68
|
+
it('backdrop click settles undefined and reports closed', async () => {
|
|
69
|
+
const { handle, overlay, onclosedCalls } = await open();
|
|
70
|
+
const backdrop = overlay.renderRoot.querySelector('#backdrop');
|
|
71
|
+
expect(backdrop, 'backdrop element').to.exist;
|
|
72
|
+
backdrop.click();
|
|
73
|
+
expect(await within(handle.result)).to.equal(undefined);
|
|
74
|
+
await settleTurn();
|
|
75
|
+
expect(handle.closed).to.equal(true);
|
|
76
|
+
expect(onclosedCalls()).to.equal(1);
|
|
77
|
+
});
|
|
78
|
+
it('the X button settles undefined and reports closed', async () => {
|
|
79
|
+
const { handle, overlay, onclosedCalls } = await open({ closable: true, title: 'x' });
|
|
80
|
+
const closer = overlay.renderRoot.querySelector('[closable][close]');
|
|
81
|
+
expect(closer, 'close button').to.exist;
|
|
82
|
+
closer.click();
|
|
83
|
+
expect(await within(handle.result)).to.equal(undefined);
|
|
84
|
+
await settleTurn();
|
|
85
|
+
expect(handle.closed).to.equal(true);
|
|
86
|
+
expect(onclosedCalls()).to.equal(1);
|
|
87
|
+
});
|
|
88
|
+
it('the first close request wins: closePopup then handle.close', async () => {
|
|
89
|
+
const { handle, content, onclosedCalls } = await open();
|
|
90
|
+
closePopup(content, 'first');
|
|
91
|
+
handle.close('second');
|
|
92
|
+
expect(await within(handle.result)).to.equal('first');
|
|
93
|
+
await settleTurn();
|
|
94
|
+
expect(onclosedCalls()).to.equal(1);
|
|
95
|
+
});
|
|
96
|
+
it('the first close request wins: handle.close twice', async () => {
|
|
97
|
+
const { handle, onclosedCalls } = await open();
|
|
98
|
+
handle.close('first');
|
|
99
|
+
handle.close('second');
|
|
100
|
+
expect(await within(handle.result)).to.equal('first');
|
|
101
|
+
await settleTurn();
|
|
102
|
+
expect(onclosedCalls()).to.equal(1);
|
|
103
|
+
});
|
|
104
|
+
it('an onclosed assigned before the change is still called once', async () => {
|
|
105
|
+
const handle = openPopup(html `<div>legacy</div>`);
|
|
106
|
+
let calls = 0;
|
|
107
|
+
handle.onclosed = () => calls++;
|
|
108
|
+
handle.close();
|
|
109
|
+
await within(handle.result);
|
|
110
|
+
await settleTurn();
|
|
111
|
+
expect(calls).to.equal(1);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
//# sourceMappingURL=popup-result.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popup-result.test.js","sourceRoot":"","sources":["../../test/popup-result.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { closePopup, openPopup } from '../src/open-popup.js'\nimport { OxFloatingOverlay } from '../src/ox-floating-overlay.js'\n\n/**\n * The popup result settles exactly once, with the right value, whichever way the popup closes -\n * for the default implementation (no @operato/layout loaded).\n *\n * The implementation's one promise is \"call onclosed once when the popup is gone\". Before this test\n * the default implementation kept it only for handle.close(): ESC, the backdrop and X removed the\n * overlay without telling the handle, so an awaited result would have hung forever.\n *\n * The same table runs for the layout implementation in @operato/layout/test. A new implementation\n * adds its own row there.\n */\n\nconst TIMEOUT = 'TIMEOUT'\n\n/** Never await a result bare - a broken implementation must fail the test, not hang the runner. */\nfunction within<T>(promise: Promise<T>, ms = 2000): Promise<T | typeof TIMEOUT> {\n return Promise.race([promise, new Promise<typeof TIMEOUT>(resolve => setTimeout(() => resolve(TIMEOUT), ms))])\n}\n\nfunction overlayOf(handle: { name: string }): OxFloatingOverlay {\n return document.getElementById(handle.name) as OxFloatingOverlay\n}\n\nasync function open(options = {}) {\n const handle = openPopup<string>(html`<div class=\"content\">content</div>`, { backdrop: true, ...options })\n let onclosedCalls = 0\n handle.onclosed = () => onclosedCalls++\n\n const overlay = overlayOf(handle)\n await overlay.updateComplete\n\n return {\n handle,\n overlay,\n content: overlay.querySelector('.content') as HTMLElement,\n onclosedCalls: () => onclosedCalls\n }\n}\n\n/** Lets any late second settle or second onclosed show up before counting. */\nconst settleTurn = () => new Promise(resolve => setTimeout(resolve, 50))\n\ndescribe('openPopup result - default implementation', () => {\n it('handle.close(value) settles with that value', async () => {\n const { handle, onclosedCalls } = await open()\n\n handle.close('saved')\n\n expect(await within(handle.result)).to.equal('saved')\n await settleTurn()\n expect(handle.closed).to.equal(true)\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('closePopup(element, value) from the content settles with that value', async () => {\n const { handle, content, onclosedCalls } = await open()\n\n closePopup(content, 'picked')\n\n expect(await within(handle.result)).to.equal('picked')\n await settleTurn()\n expect(handle.closed).to.equal(true)\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('closePopup(element) without a value settles undefined', async () => {\n const { handle, content } = await open()\n\n closePopup(content)\n\n expect(await within(handle.result)).to.equal(undefined)\n })\n\n it('ESC settles undefined and reports closed', async () => {\n const { handle, onclosedCalls } = await open({ escapable: true })\n\n document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }))\n\n expect(await within(handle.result)).to.equal(undefined)\n await settleTurn()\n expect(handle.closed).to.equal(true)\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('backdrop click settles undefined and reports closed', async () => {\n const { handle, overlay, onclosedCalls } = await open()\n\n const backdrop = overlay.renderRoot.querySelector('#backdrop') as HTMLElement\n expect(backdrop, 'backdrop element').to.exist\n backdrop.click()\n\n expect(await within(handle.result)).to.equal(undefined)\n await settleTurn()\n expect(handle.closed).to.equal(true)\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('the X button settles undefined and reports closed', async () => {\n const { handle, overlay, onclosedCalls } = await open({ closable: true, title: 'x' })\n\n const closer = overlay.renderRoot.querySelector('[closable][close]') as HTMLElement\n expect(closer, 'close button').to.exist\n closer.click()\n\n expect(await within(handle.result)).to.equal(undefined)\n await settleTurn()\n expect(handle.closed).to.equal(true)\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('the first close request wins: closePopup then handle.close', async () => {\n const { handle, content, onclosedCalls } = await open()\n\n closePopup(content, 'first')\n handle.close('second')\n\n expect(await within(handle.result)).to.equal('first')\n await settleTurn()\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('the first close request wins: handle.close twice', async () => {\n const { handle, onclosedCalls } = await open()\n\n handle.close('first')\n handle.close('second')\n\n expect(await within(handle.result)).to.equal('first')\n await settleTurn()\n expect(onclosedCalls()).to.equal(1)\n })\n\n it('an onclosed assigned before the change is still called once', async () => {\n const handle = openPopup(html`<div>legacy</div>`)\n let calls = 0\n handle.onclosed = () => calls++\n\n handle.close()\n\n await within(handle.result)\n await settleTurn()\n expect(calls).to.equal(1)\n })\n})\n"]}
|