haori 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/haori.cjs.js +10 -10
- package/dist/haori.es.js +191 -165
- package/dist/haori.iife.js +10 -10
- package/dist/index.d.ts +1 -1
- package/package.json +11 -3
- package/dist/haori.cjs.js.map +0 -1
- package/dist/haori.es.js.map +0 -1
- package/dist/haori.iife.js.map +0 -1
- package/dist/package.json +0 -70
- package/dist/src/builtins.d.ts +0 -100
- package/dist/src/builtins.d.ts.map +0 -1
- package/dist/src/builtins.js +0 -194
- package/dist/src/builtins.js.map +0 -1
- package/dist/src/core.d.ts +0 -505
- package/dist/src/core.d.ts.map +0 -1
- package/dist/src/core.js +0 -1817
- package/dist/src/core.js.map +0 -1
- package/dist/src/dev.d.ts +0 -35
- package/dist/src/dev.d.ts.map +0 -1
- package/dist/src/dev.js +0 -44
- package/dist/src/dev.js.map +0 -1
- package/dist/src/env.d.ts +0 -43
- package/dist/src/env.d.ts.map +0 -1
- package/dist/src/env.js +0 -108
- package/dist/src/env.js.map +0 -1
- package/dist/src/event.d.ts +0 -171
- package/dist/src/event.d.ts.map +0 -1
- package/dist/src/event.js +0 -235
- package/dist/src/event.js.map +0 -1
- package/dist/src/event_dispatcher.d.ts +0 -66
- package/dist/src/event_dispatcher.d.ts.map +0 -1
- package/dist/src/event_dispatcher.js +0 -167
- package/dist/src/event_dispatcher.js.map +0 -1
- package/dist/src/expression.d.ts +0 -260
- package/dist/src/expression.d.ts.map +0 -1
- package/dist/src/expression.js +0 -975
- package/dist/src/expression.js.map +0 -1
- package/dist/src/form.d.ts +0 -148
- package/dist/src/form.d.ts.map +0 -1
- package/dist/src/form.js +0 -428
- package/dist/src/form.js.map +0 -1
- package/dist/src/fragment.d.ts +0 -621
- package/dist/src/fragment.d.ts.map +0 -1
- package/dist/src/fragment.js +0 -1963
- package/dist/src/fragment.js.map +0 -1
- package/dist/src/haori.d.ts +0 -130
- package/dist/src/haori.d.ts.map +0 -1
- package/dist/src/haori.js +0 -215
- package/dist/src/haori.js.map +0 -1
- package/dist/src/import.d.ts +0 -19
- package/dist/src/import.d.ts.map +0 -1
- package/dist/src/import.js +0 -64
- package/dist/src/import.js.map +0 -1
- package/dist/src/index.d.ts +0 -27
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -30
- package/dist/src/index.js.map +0 -1
- package/dist/src/intersect.d.ts +0 -28
- package/dist/src/intersect.d.ts.map +0 -1
- package/dist/src/intersect.js +0 -201
- package/dist/src/intersect.js.map +0 -1
- package/dist/src/log.d.ts +0 -32
- package/dist/src/log.d.ts.map +0 -1
- package/dist/src/log.js +0 -43
- package/dist/src/log.js.map +0 -1
- package/dist/src/observer.d.ts +0 -24
- package/dist/src/observer.d.ts.map +0 -1
- package/dist/src/observer.js +0 -131
- package/dist/src/observer.js.map +0 -1
- package/dist/src/procedure.d.ts +0 -447
- package/dist/src/procedure.d.ts.map +0 -1
- package/dist/src/procedure.js +0 -1921
- package/dist/src/procedure.js.map +0 -1
- package/dist/src/queue.d.ts +0 -35
- package/dist/src/queue.d.ts.map +0 -1
- package/dist/src/queue.js +0 -202
- package/dist/src/queue.js.map +0 -1
- package/dist/src/url.d.ts +0 -14
- package/dist/src/url.d.ts.map +0 -1
- package/dist/src/url.js +0 -22
- package/dist/src/url.js.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.d.ts +0 -2
- package/dist/tests/bind-arg-own-binding.test.d.ts.map +0 -1
- package/dist/tests/bind-arg-own-binding.test.js +0 -63
- package/dist/tests/bind-arg-own-binding.test.js.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.d.ts +0 -2
- package/dist/tests/bindcomplete-and-scope.test.d.ts.map +0 -1
- package/dist/tests/bindcomplete-and-scope.test.js +0 -115
- package/dist/tests/bindcomplete-and-scope.test.js.map +0 -1
- package/dist/tests/builtins.test.d.ts +0 -2
- package/dist/tests/builtins.test.d.ts.map +0 -1
- package/dist/tests/builtins.test.js +0 -152
- package/dist/tests/builtins.test.js.map +0 -1
- package/dist/tests/click-attributes.test.d.ts +0 -2
- package/dist/tests/click-attributes.test.d.ts.map +0 -1
- package/dist/tests/click-attributes.test.js +0 -113
- package/dist/tests/click-attributes.test.js.map +0 -1
- package/dist/tests/click-no-disabled.test.d.ts +0 -2
- package/dist/tests/click-no-disabled.test.d.ts.map +0 -1
- package/dist/tests/click-no-disabled.test.js +0 -67
- package/dist/tests/click-no-disabled.test.js.map +0 -1
- package/dist/tests/core.test.d.ts +0 -5
- package/dist/tests/core.test.d.ts.map +0 -1
- package/dist/tests/core.test.js +0 -1056
- package/dist/tests/core.test.js.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.d.ts +0 -2
- package/dist/tests/data-bind-arg-reeval.test.d.ts.map +0 -1
- package/dist/tests/data-bind-arg-reeval.test.js +0 -119
- package/dist/tests/data-bind-arg-reeval.test.js.map +0 -1
- package/dist/tests/data-bind-merge.test.d.ts +0 -2
- package/dist/tests/data-bind-merge.test.d.ts.map +0 -1
- package/dist/tests/data-bind-merge.test.js +0 -86
- package/dist/tests/data-bind-merge.test.js.map +0 -1
- package/dist/tests/data-click-defer.test.d.ts +0 -2
- package/dist/tests/data-click-defer.test.d.ts.map +0 -1
- package/dist/tests/data-click-defer.test.js +0 -57
- package/dist/tests/data-click-defer.test.js.map +0 -1
- package/dist/tests/data-click-run.test.d.ts +0 -5
- package/dist/tests/data-click-run.test.d.ts.map +0 -1
- package/dist/tests/data-click-run.test.js +0 -109
- package/dist/tests/data-click-run.test.js.map +0 -1
- package/dist/tests/data-derive.test.d.ts +0 -2
- package/dist/tests/data-derive.test.d.ts.map +0 -1
- package/dist/tests/data-derive.test.js +0 -999
- package/dist/tests/data-derive.test.js.map +0 -1
- package/dist/tests/data-each-browserlike.test.d.ts +0 -2
- package/dist/tests/data-each-browserlike.test.d.ts.map +0 -1
- package/dist/tests/data-each-browserlike.test.js +0 -48
- package/dist/tests/data-each-browserlike.test.js.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.d.ts +0 -2
- package/dist/tests/data-each-fragment-debug.test.d.ts.map +0 -1
- package/dist/tests/data-each-fragment-debug.test.js +0 -119
- package/dist/tests/data-each-fragment-debug.test.js.map +0 -1
- package/dist/tests/data-each-table.test.d.ts +0 -2
- package/dist/tests/data-each-table.test.d.ts.map +0 -1
- package/dist/tests/data-each-table.test.js +0 -131
- package/dist/tests/data-each-table.test.js.map +0 -1
- package/dist/tests/data-each-template-recovery.test.d.ts +0 -2
- package/dist/tests/data-each-template-recovery.test.d.ts.map +0 -1
- package/dist/tests/data-each-template-recovery.test.js +0 -87
- package/dist/tests/data-each-template-recovery.test.js.map +0 -1
- package/dist/tests/data-event-prevent.test.d.ts +0 -2
- package/dist/tests/data-event-prevent.test.d.ts.map +0 -1
- package/dist/tests/data-event-prevent.test.js +0 -76
- package/dist/tests/data-event-prevent.test.js.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.d.ts +0 -2
- package/dist/tests/data-fetch-form-input-binding.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-form-input-binding.test.js +0 -202
- package/dist/tests/data-fetch-form-input-binding.test.js.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.d.ts +0 -2
- package/dist/tests/data-fetch-tbody-dom.test.d.ts.map +0 -1
- package/dist/tests/data-fetch-tbody-dom.test.js +0 -82
- package/dist/tests/data-fetch-tbody-dom.test.js.map +0 -1
- package/dist/tests/data-form-container.test.d.ts +0 -2
- package/dist/tests/data-form-container.test.d.ts.map +0 -1
- package/dist/tests/data-form-container.test.js +0 -88
- package/dist/tests/data-form-container.test.js.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.d.ts +0 -2
- package/dist/tests/data-if-dev-diagnostics.test.d.ts.map +0 -1
- package/dist/tests/data-if-dev-diagnostics.test.js +0 -55
- package/dist/tests/data-if-dev-diagnostics.test.js.map +0 -1
- package/dist/tests/data-if-falsy.test.d.ts +0 -2
- package/dist/tests/data-if-falsy.test.d.ts.map +0 -1
- package/dist/tests/data-if-falsy.test.js +0 -73
- package/dist/tests/data-if-falsy.test.js.map +0 -1
- package/dist/tests/data-load-on-show.test.d.ts +0 -2
- package/dist/tests/data-load-on-show.test.d.ts.map +0 -1
- package/dist/tests/data-load-on-show.test.js +0 -98
- package/dist/tests/data-load-on-show.test.js.map +0 -1
- package/dist/tests/derive-each-render-wait.test.d.ts +0 -2
- package/dist/tests/derive-each-render-wait.test.d.ts.map +0 -1
- package/dist/tests/derive-each-render-wait.test.js +0 -90
- package/dist/tests/derive-each-render-wait.test.js.map +0 -1
- package/dist/tests/dev.test.d.ts +0 -2
- package/dist/tests/dev.test.d.ts.map +0 -1
- package/dist/tests/dev.test.js +0 -51
- package/dist/tests/dev.test.js.map +0 -1
- package/dist/tests/each-update-event.test.d.ts +0 -2
- package/dist/tests/each-update-event.test.d.ts.map +0 -1
- package/dist/tests/each-update-event.test.js +0 -83
- package/dist/tests/each-update-event.test.js.map +0 -1
- package/dist/tests/each_arg.test.d.ts +0 -2
- package/dist/tests/each_arg.test.d.ts.map +0 -1
- package/dist/tests/each_arg.test.js +0 -41
- package/dist/tests/each_arg.test.js.map +0 -1
- package/dist/tests/env.test.d.ts +0 -2
- package/dist/tests/env.test.d.ts.map +0 -1
- package/dist/tests/env.test.js +0 -124
- package/dist/tests/env.test.js.map +0 -1
- package/dist/tests/evaluation-profile.test.d.ts +0 -2
- package/dist/tests/evaluation-profile.test.d.ts.map +0 -1
- package/dist/tests/evaluation-profile.test.js +0 -92
- package/dist/tests/evaluation-profile.test.js.map +0 -1
- package/dist/tests/event.test.d.ts +0 -2
- package/dist/tests/event.test.d.ts.map +0 -1
- package/dist/tests/event.test.js +0 -300
- package/dist/tests/event.test.js.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts +0 -2
- package/dist/tests/expression-disallowed-keyword-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-disallowed-keyword-hint.test.js +0 -45
- package/dist/tests/expression-disallowed-keyword-hint.test.js.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.d.ts +0 -2
- package/dist/tests/expression-forbidden-hint.test.d.ts.map +0 -1
- package/dist/tests/expression-forbidden-hint.test.js +0 -37
- package/dist/tests/expression-forbidden-hint.test.js.map +0 -1
- package/dist/tests/expression.test.d.ts +0 -2
- package/dist/tests/expression.test.d.ts.map +0 -1
- package/dist/tests/expression.test.js +0 -482
- package/dist/tests/expression.test.js.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts +0 -2
- package/dist/tests/fetch-and-procedure-scenarios.test.d.ts.map +0 -1
- package/dist/tests/fetch-and-procedure-scenarios.test.js +0 -195
- package/dist/tests/fetch-and-procedure-scenarios.test.js.map +0 -1
- package/dist/tests/form-number-type.test.d.ts +0 -2
- package/dist/tests/form-number-type.test.d.ts.map +0 -1
- package/dist/tests/form-number-type.test.js +0 -83
- package/dist/tests/form-number-type.test.js.map +0 -1
- package/dist/tests/form.test.d.ts +0 -2
- package/dist/tests/form.test.d.ts.map +0 -1
- package/dist/tests/form.test.js +0 -636
- package/dist/tests/form.test.js.map +0 -1
- package/dist/tests/fragment.test.d.ts +0 -2
- package/dist/tests/fragment.test.d.ts.map +0 -1
- package/dist/tests/fragment.test.js +0 -284
- package/dist/tests/fragment.test.js.map +0 -1
- package/dist/tests/haori.test.d.ts +0 -2
- package/dist/tests/haori.test.d.ts.map +0 -1
- package/dist/tests/haori.test.js +0 -149
- package/dist/tests/haori.test.js.map +0 -1
- package/dist/tests/helpers/async.d.ts +0 -20
- package/dist/tests/helpers/async.d.ts.map +0 -1
- package/dist/tests/helpers/async.js +0 -35
- package/dist/tests/helpers/async.js.map +0 -1
- package/dist/tests/import.test.d.ts +0 -2
- package/dist/tests/import.test.d.ts.map +0 -1
- package/dist/tests/import.test.js +0 -148
- package/dist/tests/import.test.js.map +0 -1
- package/dist/tests/intersect.test.d.ts +0 -2
- package/dist/tests/intersect.test.d.ts.map +0 -1
- package/dist/tests/intersect.test.js +0 -173
- package/dist/tests/intersect.test.js.map +0 -1
- package/dist/tests/log.test.d.ts +0 -2
- package/dist/tests/log.test.d.ts.map +0 -1
- package/dist/tests/log.test.js +0 -58
- package/dist/tests/log.test.js.map +0 -1
- package/dist/tests/observer.test.d.ts +0 -2
- package/dist/tests/observer.test.d.ts.map +0 -1
- package/dist/tests/observer.test.js +0 -64
- package/dist/tests/observer.test.js.map +0 -1
- package/dist/tests/placeholder-resolution.test.d.ts +0 -2
- package/dist/tests/placeholder-resolution.test.d.ts.map +0 -1
- package/dist/tests/placeholder-resolution.test.js +0 -463
- package/dist/tests/placeholder-resolution.test.js.map +0 -1
- package/dist/tests/procedure-action-operations.test.d.ts +0 -2
- package/dist/tests/procedure-action-operations.test.d.ts.map +0 -1
- package/dist/tests/procedure-action-operations.test.js +0 -1140
- package/dist/tests/procedure-action-operations.test.js.map +0 -1
- package/dist/tests/procedure-bind-append.test.d.ts +0 -2
- package/dist/tests/procedure-bind-append.test.d.ts.map +0 -1
- package/dist/tests/procedure-bind-append.test.js +0 -80
- package/dist/tests/procedure-bind-append.test.js.map +0 -1
- package/dist/tests/procedure-fetch-options.test.d.ts +0 -2
- package/dist/tests/procedure-fetch-options.test.d.ts.map +0 -1
- package/dist/tests/procedure-fetch-options.test.js +0 -255
- package/dist/tests/procedure-fetch-options.test.js.map +0 -1
- package/dist/tests/procedure.test.d.ts +0 -2
- package/dist/tests/procedure.test.d.ts.map +0 -1
- package/dist/tests/procedure.test.js +0 -137
- package/dist/tests/procedure.test.js.map +0 -1
- package/dist/tests/procedure_events.test.d.ts +0 -7
- package/dist/tests/procedure_events.test.d.ts.map +0 -1
- package/dist/tests/procedure_events.test.js +0 -261
- package/dist/tests/procedure_events.test.js.map +0 -1
- package/dist/tests/render-wait.test.d.ts +0 -2
- package/dist/tests/render-wait.test.d.ts.map +0 -1
- package/dist/tests/render-wait.test.js +0 -68
- package/dist/tests/render-wait.test.js.map +0 -1
- package/dist/tests/reset_each.test.d.ts +0 -2
- package/dist/tests/reset_each.test.d.ts.map +0 -1
- package/dist/tests/reset_each.test.js +0 -215
- package/dist/tests/reset_each.test.js.map +0 -1
- package/dist/tests/row-move.test.d.ts +0 -2
- package/dist/tests/row-move.test.d.ts.map +0 -1
- package/dist/tests/row-move.test.js +0 -220
- package/dist/tests/row-move.test.js.map +0 -1
- package/dist/tests/row-operations.test.d.ts +0 -2
- package/dist/tests/row-operations.test.d.ts.map +0 -1
- package/dist/tests/row-operations.test.js +0 -259
- package/dist/tests/row-operations.test.js.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.d.ts +0 -2
- package/dist/tests/scheduled-evaluate-wait.test.d.ts.map +0 -1
- package/dist/tests/scheduled-evaluate-wait.test.js +0 -68
- package/dist/tests/scheduled-evaluate-wait.test.js.map +0 -1
- package/dist/tests/url.test.d.ts +0 -2
- package/dist/tests/url.test.d.ts.map +0 -1
- package/dist/tests/url.test.js +0 -150
- package/dist/tests/url.test.js.map +0 -1
- package/dist/vite.config.d.ts +0 -3
- package/dist/vite.config.d.ts.map +0 -1
- package/dist/vite.config.js +0 -28
- package/dist/vite.config.js.map +0 -1
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- package/dist/vitest.config.js +0 -19
- package/dist/vitest.config.js.map +0 -1
|
@@ -1,1140 +0,0 @@
|
|
|
1
|
-
/* @vitest-environment jsdom */
|
|
2
|
-
/**
|
|
3
|
-
* Procedure のリセット、refetch、click、open/close、redirect、data-message に関するテスト
|
|
4
|
-
*/
|
|
5
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
6
|
-
import Form from '../src/form';
|
|
7
|
-
import Haori from '../src/haori';
|
|
8
|
-
import { waitForCondition, waitForDomSettled } from './helpers/async';
|
|
9
|
-
describe('Procedure action operations', () => {
|
|
10
|
-
beforeEach(async () => {
|
|
11
|
-
vi.restoreAllMocks();
|
|
12
|
-
window.Haori = Haori;
|
|
13
|
-
await import('../src/observer');
|
|
14
|
-
});
|
|
15
|
-
it('resetFragments calls Form.reset', async () => {
|
|
16
|
-
const resetSpy = vi.spyOn(Form, 'reset').mockResolvedValue(undefined);
|
|
17
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
18
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
19
|
-
});
|
|
20
|
-
const container = document.createElement('div');
|
|
21
|
-
document.body.appendChild(container);
|
|
22
|
-
const target = document.createElement('div');
|
|
23
|
-
target.id = 'to-reset';
|
|
24
|
-
container.appendChild(target);
|
|
25
|
-
const btn = document.createElement('button');
|
|
26
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/reset');
|
|
27
|
-
btn.setAttribute('data-click-reset', '#to-reset');
|
|
28
|
-
container.appendChild(btn);
|
|
29
|
-
await waitForDomSettled();
|
|
30
|
-
btn.click();
|
|
31
|
-
await waitForCondition(() => resetSpy.mock.calls.length > 0, {
|
|
32
|
-
description: 'reset action',
|
|
33
|
-
});
|
|
34
|
-
expect(resetSpy).toHaveBeenCalled();
|
|
35
|
-
container.remove();
|
|
36
|
-
});
|
|
37
|
-
it('data-click-form を伴う click 実行後に disabled 属性が残らない', async () => {
|
|
38
|
-
const fetchSpy = vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
39
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
40
|
-
});
|
|
41
|
-
const container = document.createElement('div');
|
|
42
|
-
document.body.appendChild(container);
|
|
43
|
-
const form = document.createElement('form');
|
|
44
|
-
form.id = 'search-form';
|
|
45
|
-
const button = document.createElement('button');
|
|
46
|
-
button.type = 'button';
|
|
47
|
-
button.setAttribute('data-click-fetch', 'http://api.test/search');
|
|
48
|
-
button.setAttribute('data-click-form', '#search-form');
|
|
49
|
-
button.setAttribute('data-click-data', '{"page":0}');
|
|
50
|
-
button.setAttribute('data-click-history-form', '#search-form');
|
|
51
|
-
form.appendChild(button);
|
|
52
|
-
container.appendChild(form);
|
|
53
|
-
await waitForDomSettled();
|
|
54
|
-
button.click();
|
|
55
|
-
await waitForCondition(() => fetchSpy.mock.calls.length > 0, {
|
|
56
|
-
description: 'search fetch',
|
|
57
|
-
});
|
|
58
|
-
await waitForCondition(() => !button.hasAttribute('disabled'), {
|
|
59
|
-
description: 'search button re-enabled',
|
|
60
|
-
});
|
|
61
|
-
expect(button.hasAttribute('disabled')).toBe(false);
|
|
62
|
-
container.remove();
|
|
63
|
-
});
|
|
64
|
-
it('refetchFragments invokes Procedure.run', async () => {
|
|
65
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
66
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
67
|
-
});
|
|
68
|
-
const ProcedureModule = await import('../src/procedure');
|
|
69
|
-
const runSpy = vi.spyOn(ProcedureModule.default.prototype, 'run');
|
|
70
|
-
const container = document.createElement('div');
|
|
71
|
-
document.body.appendChild(container);
|
|
72
|
-
// リフェッチ対象は data-fetch を持つ要素
|
|
73
|
-
const refetchTarget = document.createElement('div');
|
|
74
|
-
refetchTarget.id = 'refetch-target';
|
|
75
|
-
refetchTarget.setAttribute('data-fetch', 'http://api.test/refetch-target');
|
|
76
|
-
container.appendChild(refetchTarget);
|
|
77
|
-
const btn = document.createElement('button');
|
|
78
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/refetch');
|
|
79
|
-
btn.setAttribute('data-click-refetch', '#refetch-target');
|
|
80
|
-
container.appendChild(btn);
|
|
81
|
-
await waitForDomSettled();
|
|
82
|
-
btn.click();
|
|
83
|
-
await waitForDomSettled();
|
|
84
|
-
expect(runSpy).toHaveBeenCalled();
|
|
85
|
-
container.remove();
|
|
86
|
-
});
|
|
87
|
-
it('clickFragments will click returned targets (attribute-driven)', async () => {
|
|
88
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
89
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
90
|
-
});
|
|
91
|
-
const button = document.createElement('button');
|
|
92
|
-
const clickSpy = vi.spyOn(button, 'click');
|
|
93
|
-
clickSpy.mockImplementation(() => { });
|
|
94
|
-
document.body.appendChild(button);
|
|
95
|
-
const container = document.createElement('div');
|
|
96
|
-
document.body.appendChild(container);
|
|
97
|
-
const btn = document.createElement('button');
|
|
98
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/click');
|
|
99
|
-
// Instead of selector by id, push button to DOM and reference via selector
|
|
100
|
-
button.id = 'click-target';
|
|
101
|
-
btn.setAttribute('data-click-click', '#click-target');
|
|
102
|
-
container.appendChild(btn);
|
|
103
|
-
await waitForDomSettled();
|
|
104
|
-
btn.click();
|
|
105
|
-
await waitForCondition(() => clickSpy.mock.calls.length > 0, {
|
|
106
|
-
description: 'click action',
|
|
107
|
-
});
|
|
108
|
-
expect(clickSpy).toHaveBeenCalled();
|
|
109
|
-
button.remove();
|
|
110
|
-
container.remove();
|
|
111
|
-
});
|
|
112
|
-
it('data-click-copy copies form values to another form without fetch', async () => {
|
|
113
|
-
const container = document.createElement('div');
|
|
114
|
-
document.body.appendChild(container);
|
|
115
|
-
const sourceForm = document.createElement('form');
|
|
116
|
-
sourceForm.id = 'search-form';
|
|
117
|
-
const keywordInput = document.createElement('input');
|
|
118
|
-
keywordInput.name = 'keyword';
|
|
119
|
-
keywordInput.value = 'haori';
|
|
120
|
-
const pageInput = document.createElement('input');
|
|
121
|
-
pageInput.name = 'page';
|
|
122
|
-
pageInput.value = '3';
|
|
123
|
-
sourceForm.append(keywordInput, pageInput);
|
|
124
|
-
const targetForm = document.createElement('form');
|
|
125
|
-
targetForm.id = 'search-committed';
|
|
126
|
-
const committedKeywordInput = document.createElement('input');
|
|
127
|
-
committedKeywordInput.name = 'keyword';
|
|
128
|
-
const committedPageInput = document.createElement('input');
|
|
129
|
-
committedPageInput.name = 'page';
|
|
130
|
-
targetForm.append(committedKeywordInput, committedPageInput);
|
|
131
|
-
const btn = document.createElement('button');
|
|
132
|
-
btn.setAttribute('data-click-form', '#search-form');
|
|
133
|
-
btn.setAttribute('data-click-copy', '#search-committed');
|
|
134
|
-
container.append(sourceForm, targetForm, btn);
|
|
135
|
-
await waitForDomSettled();
|
|
136
|
-
btn.click();
|
|
137
|
-
await waitForCondition(() => committedKeywordInput.value === 'haori' &&
|
|
138
|
-
committedPageInput.value === '3', { description: 'copy to target form' });
|
|
139
|
-
expect(committedKeywordInput.value).toBe('haori');
|
|
140
|
-
expect(committedPageInput.value).toBe('3');
|
|
141
|
-
container.remove();
|
|
142
|
-
});
|
|
143
|
-
it('data-click-copy copies source bindingData when data-click-form is omitted', async () => {
|
|
144
|
-
const container = document.createElement('div');
|
|
145
|
-
document.body.appendChild(container);
|
|
146
|
-
const source = document.createElement('button');
|
|
147
|
-
source.textContent = 'copy';
|
|
148
|
-
source.setAttribute('data-bind', '{"keyword":"binding","page":"7"}');
|
|
149
|
-
source.setAttribute('data-click-copy', '#binding-target');
|
|
150
|
-
const target = document.createElement('div');
|
|
151
|
-
target.id = 'binding-target';
|
|
152
|
-
target.setAttribute('data-bind', '{"keyword":"old","keep":"yes"}');
|
|
153
|
-
container.append(source, target);
|
|
154
|
-
await waitForDomSettled();
|
|
155
|
-
source.click();
|
|
156
|
-
await waitForCondition(() => {
|
|
157
|
-
const bind = target.getAttribute('data-bind');
|
|
158
|
-
return bind !== null && bind.includes('"keyword":"binding"');
|
|
159
|
-
}, { description: 'copy from source bindingData' });
|
|
160
|
-
const copied = JSON.parse(target.getAttribute('data-bind') || '{}');
|
|
161
|
-
expect(copied).toMatchObject({
|
|
162
|
-
keyword: 'binding',
|
|
163
|
-
page: '7',
|
|
164
|
-
keep: 'yes',
|
|
165
|
-
});
|
|
166
|
-
container.remove();
|
|
167
|
-
});
|
|
168
|
-
it('data-click-copy logs an error and skips copy when the selector does not match', async () => {
|
|
169
|
-
const container = document.createElement('div');
|
|
170
|
-
document.body.appendChild(container);
|
|
171
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
172
|
-
const source = document.createElement('button');
|
|
173
|
-
source.textContent = 'copy';
|
|
174
|
-
source.setAttribute('data-bind', '{"keyword":"binding"}');
|
|
175
|
-
source.setAttribute('data-click-copy', '#missing-target');
|
|
176
|
-
const target = document.createElement('div');
|
|
177
|
-
target.id = 'existing-target';
|
|
178
|
-
target.setAttribute('data-bind', '{"keyword":"old","keep":"yes"}');
|
|
179
|
-
container.append(source, target);
|
|
180
|
-
await waitForDomSettled();
|
|
181
|
-
source.click();
|
|
182
|
-
await waitForCondition(() => errorSpy.mock.calls.some(call => call.some(arg => typeof arg === 'string' &&
|
|
183
|
-
arg.includes('Element not found: #missing-target'))), { description: 'missing copy target error' });
|
|
184
|
-
const copied = JSON.parse(target.getAttribute('data-bind') || '{}');
|
|
185
|
-
expect(copied).toMatchObject({
|
|
186
|
-
keyword: 'old',
|
|
187
|
-
keep: 'yes',
|
|
188
|
-
});
|
|
189
|
-
errorSpy.mockRestore();
|
|
190
|
-
container.remove();
|
|
191
|
-
});
|
|
192
|
-
it('data-click-copy-source copies from specified form to destination', async () => {
|
|
193
|
-
const container = document.createElement('div');
|
|
194
|
-
document.body.appendChild(container);
|
|
195
|
-
const sourceForm = document.createElement('form');
|
|
196
|
-
sourceForm.id = 'copy-source-specified-form';
|
|
197
|
-
const keywordInput = document.createElement('input');
|
|
198
|
-
keywordInput.name = 'keyword';
|
|
199
|
-
keywordInput.value = 'from-source';
|
|
200
|
-
const pageInput = document.createElement('input');
|
|
201
|
-
pageInput.name = 'page';
|
|
202
|
-
pageInput.value = '5';
|
|
203
|
-
sourceForm.append(keywordInput, pageInput);
|
|
204
|
-
const destForm = document.createElement('form');
|
|
205
|
-
destForm.id = 'copy-dest-specified-form';
|
|
206
|
-
const destKeyword = document.createElement('input');
|
|
207
|
-
destKeyword.name = 'keyword';
|
|
208
|
-
const destPage = document.createElement('input');
|
|
209
|
-
destPage.name = 'page';
|
|
210
|
-
destForm.append(destKeyword, destPage);
|
|
211
|
-
const btn = document.createElement('button');
|
|
212
|
-
btn.setAttribute('data-click-copy-source', '#copy-source-specified-form');
|
|
213
|
-
btn.setAttribute('data-click-copy', '#copy-dest-specified-form');
|
|
214
|
-
container.append(sourceForm, destForm, btn);
|
|
215
|
-
await waitForDomSettled();
|
|
216
|
-
btn.click();
|
|
217
|
-
await waitForCondition(() => destKeyword.value === 'from-source' && destPage.value === '5', { description: 'copy from specified source form' });
|
|
218
|
-
expect(destKeyword.value).toBe('from-source');
|
|
219
|
-
expect(destPage.value).toBe('5');
|
|
220
|
-
container.remove();
|
|
221
|
-
});
|
|
222
|
-
it('data-click-copy-source copies binding data from specified non-form element', async () => {
|
|
223
|
-
const container = document.createElement('div');
|
|
224
|
-
document.body.appendChild(container);
|
|
225
|
-
const source = document.createElement('div');
|
|
226
|
-
source.id = 'copy-source-binding-div';
|
|
227
|
-
source.setAttribute('data-bind', '{"keyword":"from-binding","page":"9"}');
|
|
228
|
-
const dest = document.createElement('div');
|
|
229
|
-
dest.id = 'copy-dest-binding-div';
|
|
230
|
-
dest.setAttribute('data-bind', '{"keyword":"old","keep":"yes"}');
|
|
231
|
-
const btn = document.createElement('button');
|
|
232
|
-
btn.setAttribute('data-click-copy-source', '#copy-source-binding-div');
|
|
233
|
-
btn.setAttribute('data-click-copy', '#copy-dest-binding-div');
|
|
234
|
-
container.append(source, dest, btn);
|
|
235
|
-
await waitForDomSettled();
|
|
236
|
-
btn.click();
|
|
237
|
-
await waitForCondition(() => {
|
|
238
|
-
const bind = dest.getAttribute('data-bind');
|
|
239
|
-
return bind !== null && bind.includes('"keyword":"from-binding"');
|
|
240
|
-
}, { description: 'copy from specified source binding data' });
|
|
241
|
-
const copied = JSON.parse(dest.getAttribute('data-bind') || '{}');
|
|
242
|
-
expect(copied).toMatchObject({ keyword: 'from-binding', page: '9', keep: 'yes' });
|
|
243
|
-
container.remove();
|
|
244
|
-
});
|
|
245
|
-
it('data-click-copy-source takes precedence over data-click-form', async () => {
|
|
246
|
-
const container = document.createElement('div');
|
|
247
|
-
document.body.appendChild(container);
|
|
248
|
-
const triggerForm = document.createElement('form');
|
|
249
|
-
triggerForm.id = 'copy-trigger-form';
|
|
250
|
-
const triggerInput = document.createElement('input');
|
|
251
|
-
triggerInput.name = 'keyword';
|
|
252
|
-
triggerInput.value = 'trigger-value';
|
|
253
|
-
triggerForm.appendChild(triggerInput);
|
|
254
|
-
const sourceForm = document.createElement('form');
|
|
255
|
-
sourceForm.id = 'copy-source-priority-form';
|
|
256
|
-
const sourceInput = document.createElement('input');
|
|
257
|
-
sourceInput.name = 'keyword';
|
|
258
|
-
sourceInput.value = 'source-value';
|
|
259
|
-
sourceForm.appendChild(sourceInput);
|
|
260
|
-
const dest = document.createElement('div');
|
|
261
|
-
dest.id = 'copy-dest-priority-div';
|
|
262
|
-
dest.setAttribute('data-bind', '{}');
|
|
263
|
-
const btn = document.createElement('button');
|
|
264
|
-
btn.setAttribute('data-click-form', '#copy-trigger-form');
|
|
265
|
-
btn.setAttribute('data-click-copy-source', '#copy-source-priority-form');
|
|
266
|
-
btn.setAttribute('data-click-copy', '#copy-dest-priority-div');
|
|
267
|
-
container.append(triggerForm, sourceForm, dest, btn);
|
|
268
|
-
await waitForDomSettled();
|
|
269
|
-
btn.click();
|
|
270
|
-
await waitForCondition(() => {
|
|
271
|
-
const bind = dest.getAttribute('data-bind');
|
|
272
|
-
return bind !== null && bind.includes('"keyword":"source-value"');
|
|
273
|
-
}, { description: 'copy-source takes precedence over form' });
|
|
274
|
-
const copied = JSON.parse(dest.getAttribute('data-bind') || '{}');
|
|
275
|
-
expect(copied).toMatchObject({ keyword: 'source-value' });
|
|
276
|
-
container.remove();
|
|
277
|
-
});
|
|
278
|
-
it('data-click-copy-source logs error when selector does not match', async () => {
|
|
279
|
-
const container = document.createElement('div');
|
|
280
|
-
document.body.appendChild(container);
|
|
281
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
282
|
-
const dest = document.createElement('div');
|
|
283
|
-
dest.id = 'copy-dest-missing-source';
|
|
284
|
-
dest.setAttribute('data-bind', '{"keep":"yes"}');
|
|
285
|
-
const btn = document.createElement('button');
|
|
286
|
-
btn.setAttribute('data-bind', '{"keyword":"trigger"}');
|
|
287
|
-
btn.setAttribute('data-click-copy-source', '#no-such-source');
|
|
288
|
-
btn.setAttribute('data-click-copy', '#copy-dest-missing-source');
|
|
289
|
-
container.append(dest, btn);
|
|
290
|
-
await waitForDomSettled();
|
|
291
|
-
btn.click();
|
|
292
|
-
await waitForCondition(() => errorSpy.mock.calls.some(call => call.some(arg => typeof arg === 'string' &&
|
|
293
|
-
arg.includes('Element not found: #no-such-source'))), { description: 'missing copy-source error' });
|
|
294
|
-
const copied = JSON.parse(dest.getAttribute('data-bind') || '{}');
|
|
295
|
-
expect(copied).toMatchObject({ keep: 'yes' });
|
|
296
|
-
errorSpy.mockRestore();
|
|
297
|
-
container.remove();
|
|
298
|
-
});
|
|
299
|
-
it('data-click-copy-source="" (empty) uses the trigger element itself as source', async () => {
|
|
300
|
-
const container = document.createElement('div');
|
|
301
|
-
document.body.appendChild(container);
|
|
302
|
-
const btn = document.createElement('button');
|
|
303
|
-
btn.setAttribute('data-bind', '{"keyword":"self-source","page":"1"}');
|
|
304
|
-
btn.setAttribute('data-click-copy-source', '');
|
|
305
|
-
btn.setAttribute('data-click-copy', '#copy-dest-self-source');
|
|
306
|
-
const dest = document.createElement('div');
|
|
307
|
-
dest.id = 'copy-dest-self-source';
|
|
308
|
-
dest.setAttribute('data-bind', '{"keep":"yes"}');
|
|
309
|
-
container.append(btn, dest);
|
|
310
|
-
await waitForDomSettled();
|
|
311
|
-
btn.click();
|
|
312
|
-
await waitForCondition(() => {
|
|
313
|
-
const bind = dest.getAttribute('data-bind');
|
|
314
|
-
return bind !== null && bind.includes('"keyword":"self-source"');
|
|
315
|
-
}, { description: 'copy from self (empty selector)' });
|
|
316
|
-
const copied = JSON.parse(dest.getAttribute('data-bind') || '{}');
|
|
317
|
-
expect(copied).toMatchObject({ keyword: 'self-source', page: '1', keep: 'yes' });
|
|
318
|
-
container.remove();
|
|
319
|
-
});
|
|
320
|
-
it('data-click-copy-params copies only selected keys and preserves target data', async () => {
|
|
321
|
-
const container = document.createElement('div');
|
|
322
|
-
document.body.appendChild(container);
|
|
323
|
-
const sourceForm = document.createElement('form');
|
|
324
|
-
sourceForm.id = 'copy-source-form';
|
|
325
|
-
const keywordInput = document.createElement('input');
|
|
326
|
-
keywordInput.name = 'keyword';
|
|
327
|
-
keywordInput.value = 'next';
|
|
328
|
-
const pageInput = document.createElement('input');
|
|
329
|
-
pageInput.name = 'page';
|
|
330
|
-
pageInput.value = '2';
|
|
331
|
-
sourceForm.append(keywordInput, pageInput);
|
|
332
|
-
const target = document.createElement('div');
|
|
333
|
-
target.id = 'copy-target';
|
|
334
|
-
target.setAttribute('data-bind', '{"keyword":"old","keep":"yes"}');
|
|
335
|
-
const btn = document.createElement('button');
|
|
336
|
-
btn.setAttribute('data-click-form', '#copy-source-form');
|
|
337
|
-
btn.setAttribute('data-click-copy', '#copy-target');
|
|
338
|
-
btn.setAttribute('data-click-copy-params', 'page');
|
|
339
|
-
container.append(sourceForm, target, btn);
|
|
340
|
-
await waitForDomSettled();
|
|
341
|
-
btn.click();
|
|
342
|
-
await waitForCondition(() => {
|
|
343
|
-
const bind = target.getAttribute('data-bind');
|
|
344
|
-
return bind !== null && bind.includes('"page":"2"');
|
|
345
|
-
}, { description: 'copy selected params' });
|
|
346
|
-
const copied = JSON.parse(target.getAttribute('data-bind') || '{}');
|
|
347
|
-
expect(copied).toMatchObject({
|
|
348
|
-
keyword: 'old',
|
|
349
|
-
keep: 'yes',
|
|
350
|
-
page: '2',
|
|
351
|
-
});
|
|
352
|
-
container.remove();
|
|
353
|
-
});
|
|
354
|
-
it('data-click-copy-params excludes keys when only exclusions are specified', async () => {
|
|
355
|
-
const container = document.createElement('div');
|
|
356
|
-
document.body.appendChild(container);
|
|
357
|
-
const sourceForm = document.createElement('form');
|
|
358
|
-
sourceForm.id = 'copy-exclude-source-form';
|
|
359
|
-
const keywordInput = document.createElement('input');
|
|
360
|
-
keywordInput.name = 'keyword';
|
|
361
|
-
keywordInput.value = 'next';
|
|
362
|
-
const pageInput = document.createElement('input');
|
|
363
|
-
pageInput.name = 'page';
|
|
364
|
-
pageInput.value = '2';
|
|
365
|
-
const sortInput = document.createElement('input');
|
|
366
|
-
sortInput.name = 'sort';
|
|
367
|
-
sortInput.value = 'desc';
|
|
368
|
-
sourceForm.append(keywordInput, pageInput, sortInput);
|
|
369
|
-
const target = document.createElement('div');
|
|
370
|
-
target.id = 'copy-exclude-target';
|
|
371
|
-
target.setAttribute('data-bind', '{"keyword":"old","keep":"yes"}');
|
|
372
|
-
const btn = document.createElement('button');
|
|
373
|
-
btn.setAttribute('data-click-form', '#copy-exclude-source-form');
|
|
374
|
-
btn.setAttribute('data-click-copy', '#copy-exclude-target');
|
|
375
|
-
btn.setAttribute('data-click-copy-params', '!page&!sort');
|
|
376
|
-
container.append(sourceForm, target, btn);
|
|
377
|
-
await waitForDomSettled();
|
|
378
|
-
btn.click();
|
|
379
|
-
await waitForCondition(() => {
|
|
380
|
-
const bind = target.getAttribute('data-bind');
|
|
381
|
-
return bind !== null && bind.includes('"keyword":"next"');
|
|
382
|
-
}, { description: 'copy with exclusions only' });
|
|
383
|
-
const copied = JSON.parse(target.getAttribute('data-bind') || '{}');
|
|
384
|
-
expect(copied).toMatchObject({
|
|
385
|
-
keyword: 'next',
|
|
386
|
-
keep: 'yes',
|
|
387
|
-
});
|
|
388
|
-
expect(copied).not.toHaveProperty('page');
|
|
389
|
-
expect(copied).not.toHaveProperty('sort');
|
|
390
|
-
container.remove();
|
|
391
|
-
});
|
|
392
|
-
it('data-click-copy-params can combine includes and excludes', async () => {
|
|
393
|
-
const container = document.createElement('div');
|
|
394
|
-
document.body.appendChild(container);
|
|
395
|
-
const sourceForm = document.createElement('form');
|
|
396
|
-
sourceForm.id = 'copy-mixed-source-form';
|
|
397
|
-
const keywordInput = document.createElement('input');
|
|
398
|
-
keywordInput.name = 'keyword';
|
|
399
|
-
keywordInput.value = 'next';
|
|
400
|
-
const pageInput = document.createElement('input');
|
|
401
|
-
pageInput.name = 'page';
|
|
402
|
-
pageInput.value = '2';
|
|
403
|
-
const sortInput = document.createElement('input');
|
|
404
|
-
sortInput.name = 'sort';
|
|
405
|
-
sortInput.value = 'desc';
|
|
406
|
-
sourceForm.append(keywordInput, pageInput, sortInput);
|
|
407
|
-
const target = document.createElement('div');
|
|
408
|
-
target.id = 'copy-mixed-target';
|
|
409
|
-
target.setAttribute('data-bind', '{"keyword":"old","keep":"yes"}');
|
|
410
|
-
const btn = document.createElement('button');
|
|
411
|
-
btn.setAttribute('data-click-form', '#copy-mixed-source-form');
|
|
412
|
-
btn.setAttribute('data-click-copy', '#copy-mixed-target');
|
|
413
|
-
btn.setAttribute('data-click-copy-params', 'keyword&page&!page');
|
|
414
|
-
container.append(sourceForm, target, btn);
|
|
415
|
-
await waitForDomSettled();
|
|
416
|
-
btn.click();
|
|
417
|
-
await waitForCondition(() => {
|
|
418
|
-
const bind = target.getAttribute('data-bind');
|
|
419
|
-
return bind !== null && bind.includes('"keyword":"next"');
|
|
420
|
-
}, { description: 'copy with include and exclude' });
|
|
421
|
-
const copied = JSON.parse(target.getAttribute('data-bind') || '{}');
|
|
422
|
-
expect(copied).toMatchObject({
|
|
423
|
-
keyword: 'next',
|
|
424
|
-
keep: 'yes',
|
|
425
|
-
});
|
|
426
|
-
expect(copied).not.toHaveProperty('page');
|
|
427
|
-
expect(copied).not.toHaveProperty('sort');
|
|
428
|
-
container.remove();
|
|
429
|
-
});
|
|
430
|
-
it('data-click-copy-params ignores empty tokens and trims whitespace', async () => {
|
|
431
|
-
const container = document.createElement('div');
|
|
432
|
-
document.body.appendChild(container);
|
|
433
|
-
const sourceForm = document.createElement('form');
|
|
434
|
-
sourceForm.id = 'copy-trim-source-form';
|
|
435
|
-
const keywordInput = document.createElement('input');
|
|
436
|
-
keywordInput.name = 'keyword';
|
|
437
|
-
keywordInput.value = 'next';
|
|
438
|
-
const pageInput = document.createElement('input');
|
|
439
|
-
pageInput.name = 'page';
|
|
440
|
-
pageInput.value = '2';
|
|
441
|
-
sourceForm.append(keywordInput, pageInput);
|
|
442
|
-
const target = document.createElement('div');
|
|
443
|
-
target.id = 'copy-trim-target';
|
|
444
|
-
target.setAttribute('data-bind', '{"keep":"yes"}');
|
|
445
|
-
const btn = document.createElement('button');
|
|
446
|
-
btn.setAttribute('data-click-form', '#copy-trim-source-form');
|
|
447
|
-
btn.setAttribute('data-click-copy', '#copy-trim-target');
|
|
448
|
-
btn.setAttribute('data-click-copy-params', '&& keyword & !page ');
|
|
449
|
-
container.append(sourceForm, target, btn);
|
|
450
|
-
await waitForDomSettled();
|
|
451
|
-
btn.click();
|
|
452
|
-
await waitForCondition(() => {
|
|
453
|
-
const bind = target.getAttribute('data-bind');
|
|
454
|
-
return bind !== null && bind.includes('"keyword":"next"');
|
|
455
|
-
}, { description: 'copy with trimmed params' });
|
|
456
|
-
const copied = JSON.parse(target.getAttribute('data-bind') || '{}');
|
|
457
|
-
expect(copied).toMatchObject({
|
|
458
|
-
keyword: 'next',
|
|
459
|
-
keep: 'yes',
|
|
460
|
-
});
|
|
461
|
-
expect(copied).not.toHaveProperty('page');
|
|
462
|
-
container.remove();
|
|
463
|
-
});
|
|
464
|
-
it('data-click-reset executes before data-click-copy', async () => {
|
|
465
|
-
const container = document.createElement('div');
|
|
466
|
-
document.body.appendChild(container);
|
|
467
|
-
const sourceForm = document.createElement('form');
|
|
468
|
-
sourceForm.id = 'reset-source-form';
|
|
469
|
-
const keywordInput = document.createElement('input');
|
|
470
|
-
keywordInput.name = 'keyword';
|
|
471
|
-
sourceForm.appendChild(keywordInput);
|
|
472
|
-
keywordInput.value = 'dirty';
|
|
473
|
-
const targetForm = document.createElement('form');
|
|
474
|
-
targetForm.id = 'reset-copy-target';
|
|
475
|
-
const committedKeywordInput = document.createElement('input');
|
|
476
|
-
committedKeywordInput.name = 'keyword';
|
|
477
|
-
committedKeywordInput.value = 'stale';
|
|
478
|
-
targetForm.appendChild(committedKeywordInput);
|
|
479
|
-
const btn = document.createElement('button');
|
|
480
|
-
btn.setAttribute('data-click-form', '#reset-source-form');
|
|
481
|
-
btn.setAttribute('data-click-reset', '#reset-source-form');
|
|
482
|
-
btn.setAttribute('data-click-copy', '#reset-copy-target');
|
|
483
|
-
container.append(sourceForm, targetForm, btn);
|
|
484
|
-
await waitForDomSettled();
|
|
485
|
-
btn.click();
|
|
486
|
-
await waitForCondition(() => keywordInput.value === '' && committedKeywordInput.value === '', { description: 'reset before copy' });
|
|
487
|
-
expect(keywordInput.value).toBe('');
|
|
488
|
-
expect(committedKeywordInput.value).toBe('');
|
|
489
|
-
container.remove();
|
|
490
|
-
});
|
|
491
|
-
it('open/close call Haori.openDialog/closeDialog', async () => {
|
|
492
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
493
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
494
|
-
});
|
|
495
|
-
const dialog = document.createElement('dialog');
|
|
496
|
-
dialog.id = 'dlg';
|
|
497
|
-
document.body.appendChild(dialog);
|
|
498
|
-
const openSpy = vi.spyOn(Haori, 'openDialog');
|
|
499
|
-
openSpy.mockResolvedValue(undefined);
|
|
500
|
-
const closeSpy = vi.spyOn(Haori, 'closeDialog');
|
|
501
|
-
closeSpy.mockResolvedValue(undefined);
|
|
502
|
-
const container = document.createElement('div');
|
|
503
|
-
document.body.appendChild(container);
|
|
504
|
-
const btn = document.createElement('button');
|
|
505
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/dialog');
|
|
506
|
-
btn.setAttribute('data-click-open', '#dlg');
|
|
507
|
-
btn.setAttribute('data-click-close', '#dlg');
|
|
508
|
-
container.appendChild(btn);
|
|
509
|
-
await waitForDomSettled();
|
|
510
|
-
btn.click();
|
|
511
|
-
await waitForCondition(() => openSpy.mock.calls.length > 0 && closeSpy.mock.calls.length > 0, { description: 'open and close actions' });
|
|
512
|
-
expect(openSpy).toHaveBeenCalled();
|
|
513
|
-
expect(closeSpy).toHaveBeenCalled();
|
|
514
|
-
dialog.remove();
|
|
515
|
-
container.remove();
|
|
516
|
-
});
|
|
517
|
-
it('data-click-open delegates to window.Haori for Bootstrap modal targets', async () => {
|
|
518
|
-
const typedWindow = window;
|
|
519
|
-
const originalHaori = typedWindow.Haori;
|
|
520
|
-
const openSpy = vi.fn().mockResolvedValue(undefined);
|
|
521
|
-
const closeSpy = vi.fn().mockResolvedValue(undefined);
|
|
522
|
-
typedWindow.Haori = {
|
|
523
|
-
dialog: vi.fn().mockResolvedValue(undefined),
|
|
524
|
-
confirm: vi.fn().mockResolvedValue(true),
|
|
525
|
-
toast: vi.fn().mockResolvedValue(undefined),
|
|
526
|
-
openDialog: openSpy,
|
|
527
|
-
closeDialog: closeSpy,
|
|
528
|
-
addErrorMessage: vi.fn().mockResolvedValue(undefined),
|
|
529
|
-
};
|
|
530
|
-
const modal = document.createElement('div');
|
|
531
|
-
modal.id = 'user-modal';
|
|
532
|
-
modal.className = 'modal';
|
|
533
|
-
document.body.appendChild(modal);
|
|
534
|
-
const container = document.createElement('div');
|
|
535
|
-
document.body.appendChild(container);
|
|
536
|
-
const btn = document.createElement('button');
|
|
537
|
-
btn.setAttribute('data-click-open', '#user-modal');
|
|
538
|
-
btn.setAttribute('data-click-close', '#user-modal');
|
|
539
|
-
container.appendChild(btn);
|
|
540
|
-
await waitForDomSettled();
|
|
541
|
-
btn.click();
|
|
542
|
-
await waitForDomSettled();
|
|
543
|
-
expect(openSpy).toHaveBeenCalledWith(modal);
|
|
544
|
-
expect(closeSpy).toHaveBeenCalledWith(modal);
|
|
545
|
-
if (originalHaori === undefined) {
|
|
546
|
-
Reflect.deleteProperty(window, 'Haori');
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
typedWindow.Haori = originalHaori;
|
|
550
|
-
}
|
|
551
|
-
modal.remove();
|
|
552
|
-
container.remove();
|
|
553
|
-
});
|
|
554
|
-
it('redirectUrl sets window.location.href (attribute-driven)', async () => {
|
|
555
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
556
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
557
|
-
});
|
|
558
|
-
// jsdom does not implement full navigation; use hash redirect to avoid navigation error
|
|
559
|
-
const url = '#/redirected';
|
|
560
|
-
const container = document.createElement('div');
|
|
561
|
-
document.body.appendChild(container);
|
|
562
|
-
const btn = document.createElement('button');
|
|
563
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/redirect');
|
|
564
|
-
btn.setAttribute('data-click-redirect', url);
|
|
565
|
-
container.appendChild(btn);
|
|
566
|
-
await waitForDomSettled();
|
|
567
|
-
btn.click();
|
|
568
|
-
await waitForCondition(() => window.location.href.endsWith(url), {
|
|
569
|
-
description: 'redirect url',
|
|
570
|
-
});
|
|
571
|
-
const redirected = window.location.href.endsWith(url);
|
|
572
|
-
expect(redirected).toBeTruthy();
|
|
573
|
-
container.remove();
|
|
574
|
-
});
|
|
575
|
-
it('handleFetchError sets data-message on parent via Haori.addErrorMessage (attribute-driven)', async () => {
|
|
576
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
577
|
-
return Promise.resolve(new Response('Server failed', {
|
|
578
|
-
status: 400,
|
|
579
|
-
statusText: 'Bad',
|
|
580
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
581
|
-
}));
|
|
582
|
-
});
|
|
583
|
-
const parent = document.createElement('div');
|
|
584
|
-
const input = document.createElement('input');
|
|
585
|
-
parent.appendChild(input);
|
|
586
|
-
document.body.appendChild(parent);
|
|
587
|
-
const btn = document.createElement('button');
|
|
588
|
-
// ボタンは parent 内に入れることで、Haori.addErrorMessage が親要素に data-message を付加する
|
|
589
|
-
parent.appendChild(btn);
|
|
590
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/error');
|
|
591
|
-
await waitForDomSettled();
|
|
592
|
-
btn.click();
|
|
593
|
-
await waitForCondition(() => parent.getAttribute('data-message') === 'Server failed', { description: 'error message' });
|
|
594
|
-
// Haori.addErrorMessage sets data-message on parent element
|
|
595
|
-
expect(parent.getAttribute('data-message')).toBe('Server failed');
|
|
596
|
-
parent.remove();
|
|
597
|
-
});
|
|
598
|
-
it('JSON field errors delegate to window.Haori.addErrorMessage', async () => {
|
|
599
|
-
const typedWindow = window;
|
|
600
|
-
const originalHaori = typedWindow.Haori;
|
|
601
|
-
const addErrorMessageSpy = vi
|
|
602
|
-
.fn()
|
|
603
|
-
.mockImplementation(async (target, message) => {
|
|
604
|
-
target.setAttribute('data-test-message', message);
|
|
605
|
-
});
|
|
606
|
-
typedWindow.Haori = {
|
|
607
|
-
dialog: vi.fn().mockResolvedValue(undefined),
|
|
608
|
-
confirm: vi.fn().mockResolvedValue(true),
|
|
609
|
-
toast: vi.fn().mockResolvedValue(undefined),
|
|
610
|
-
openDialog: vi.fn().mockResolvedValue(undefined),
|
|
611
|
-
closeDialog: vi.fn().mockResolvedValue(undefined),
|
|
612
|
-
addErrorMessage: addErrorMessageSpy,
|
|
613
|
-
clearMessages: vi.fn().mockResolvedValue(undefined),
|
|
614
|
-
};
|
|
615
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
616
|
-
return Promise.resolve(new Response(JSON.stringify({ errors: { email: 'メールアドレスが不正です' } }), {
|
|
617
|
-
status: 400,
|
|
618
|
-
statusText: 'Bad Request',
|
|
619
|
-
headers: { 'Content-Type': 'application/json' },
|
|
620
|
-
}));
|
|
621
|
-
});
|
|
622
|
-
const form = document.createElement('form');
|
|
623
|
-
const emailInput = document.createElement('input');
|
|
624
|
-
emailInput.name = 'email';
|
|
625
|
-
form.appendChild(emailInput);
|
|
626
|
-
const btn = document.createElement('button');
|
|
627
|
-
btn.type = 'button';
|
|
628
|
-
btn.setAttribute('data-click-form', '');
|
|
629
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/json-error');
|
|
630
|
-
form.appendChild(btn);
|
|
631
|
-
document.body.appendChild(form);
|
|
632
|
-
await waitForDomSettled();
|
|
633
|
-
btn.click();
|
|
634
|
-
await waitForCondition(() => addErrorMessageSpy.mock.calls.length > 0, {
|
|
635
|
-
description: 'json field error delegation',
|
|
636
|
-
});
|
|
637
|
-
expect(addErrorMessageSpy).toHaveBeenCalledWith(emailInput, 'メールアドレスが不正です');
|
|
638
|
-
if (originalHaori === undefined) {
|
|
639
|
-
Reflect.deleteProperty(window, 'Haori');
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
typedWindow.Haori = originalHaori;
|
|
643
|
-
}
|
|
644
|
-
form.remove();
|
|
645
|
-
});
|
|
646
|
-
// -----------------------------------------------------------------------
|
|
647
|
-
// data-click-scroll-error
|
|
648
|
-
// -----------------------------------------------------------------------
|
|
649
|
-
describe('data-click-scroll-error', () => {
|
|
650
|
-
let scrollIntoViewSpy;
|
|
651
|
-
beforeEach(() => {
|
|
652
|
-
// jsdom は scrollIntoView を実装していないため先に定義してからスパイする
|
|
653
|
-
Element.prototype.scrollIntoView = () => { };
|
|
654
|
-
scrollIntoViewSpy = vi
|
|
655
|
-
.spyOn(Element.prototype, 'scrollIntoView')
|
|
656
|
-
.mockImplementation(() => { });
|
|
657
|
-
});
|
|
658
|
-
afterEach(() => {
|
|
659
|
-
// vi.restoreAllMocks() は直接代入した () => {} へ戻すだけなので、
|
|
660
|
-
// afterEach でプロパティ自体を削除して jsdom の元の状態(未定義)に戻す
|
|
661
|
-
delete Element.prototype
|
|
662
|
-
.scrollIntoView;
|
|
663
|
-
});
|
|
664
|
-
it('JSON フィールドエラー: エラー要素の scrollIntoView が呼ばれる', async () => {
|
|
665
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response(JSON.stringify({ errors: { email: 'メールアドレスが不正です' } }), { status: 422, headers: { 'Content-Type': 'application/json' } })));
|
|
666
|
-
const form = document.createElement('form');
|
|
667
|
-
const emailWrapper = document.createElement('div');
|
|
668
|
-
const emailInput = document.createElement('input');
|
|
669
|
-
emailInput.name = 'email';
|
|
670
|
-
emailWrapper.appendChild(emailInput);
|
|
671
|
-
const btn = document.createElement('button');
|
|
672
|
-
btn.type = 'button';
|
|
673
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/json-field-error');
|
|
674
|
-
btn.setAttribute('data-click-scroll-error', '');
|
|
675
|
-
form.append(emailWrapper, btn);
|
|
676
|
-
document.body.appendChild(form);
|
|
677
|
-
await waitForDomSettled();
|
|
678
|
-
btn.click();
|
|
679
|
-
await waitForCondition(() => emailWrapper.getAttribute('data-message-level') === 'error', { description: 'field error message set' });
|
|
680
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledWith({
|
|
681
|
-
behavior: 'smooth',
|
|
682
|
-
block: 'nearest',
|
|
683
|
-
});
|
|
684
|
-
form.remove();
|
|
685
|
-
});
|
|
686
|
-
it('JSON entries が空(汎用メッセージ): scrollIntoView が呼ばれる', async () => {
|
|
687
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('{}', {
|
|
688
|
-
status: 500,
|
|
689
|
-
headers: { 'Content-Type': 'application/json' },
|
|
690
|
-
})));
|
|
691
|
-
const form = document.createElement('form');
|
|
692
|
-
const btn = document.createElement('button');
|
|
693
|
-
btn.type = 'button';
|
|
694
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/json-empty-error');
|
|
695
|
-
btn.setAttribute('data-click-scroll-error', '');
|
|
696
|
-
form.appendChild(btn);
|
|
697
|
-
document.body.appendChild(form);
|
|
698
|
-
await waitForDomSettled();
|
|
699
|
-
btn.click();
|
|
700
|
-
await waitForCondition(() => form.getAttribute('data-message-level') === 'error', { description: 'general error message set (json empty)' });
|
|
701
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledWith({
|
|
702
|
-
behavior: 'smooth',
|
|
703
|
-
block: 'nearest',
|
|
704
|
-
});
|
|
705
|
-
form.remove();
|
|
706
|
-
});
|
|
707
|
-
it('text/plain フォールバック: scrollIntoView が呼ばれる', async () => {
|
|
708
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('Internal Server Error', {
|
|
709
|
-
status: 500,
|
|
710
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
711
|
-
})));
|
|
712
|
-
const form = document.createElement('form');
|
|
713
|
-
const btn = document.createElement('button');
|
|
714
|
-
btn.type = 'button';
|
|
715
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/text-error');
|
|
716
|
-
btn.setAttribute('data-click-scroll-error', '');
|
|
717
|
-
form.appendChild(btn);
|
|
718
|
-
document.body.appendChild(form);
|
|
719
|
-
await waitForDomSettled();
|
|
720
|
-
btn.click();
|
|
721
|
-
await waitForCondition(() => form.getAttribute('data-message-level') === 'error', { description: 'text fallback error message set' });
|
|
722
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledWith({
|
|
723
|
-
behavior: 'smooth',
|
|
724
|
-
block: 'nearest',
|
|
725
|
-
});
|
|
726
|
-
form.remove();
|
|
727
|
-
});
|
|
728
|
-
it('non-form target: addErrorMessage が parentElement に付与したエラー要素へ scrollIntoView が呼ばれる', async () => {
|
|
729
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('Server Error', {
|
|
730
|
-
status: 500,
|
|
731
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
732
|
-
})));
|
|
733
|
-
// フォームではなく div の中にボタンを置く
|
|
734
|
-
const container = document.createElement('div');
|
|
735
|
-
const btn = document.createElement('button');
|
|
736
|
-
btn.type = 'button';
|
|
737
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/non-form-error');
|
|
738
|
-
btn.setAttribute('data-click-scroll-error', '');
|
|
739
|
-
container.appendChild(btn);
|
|
740
|
-
document.body.appendChild(container);
|
|
741
|
-
await waitForDomSettled();
|
|
742
|
-
btn.click();
|
|
743
|
-
// addErrorMessage は非フォーム target の parentElement にエラーを付与する
|
|
744
|
-
await waitForCondition(() => container.getAttribute('data-message-level') === 'error', { description: 'error set on parentElement (container)' });
|
|
745
|
-
// container(btn.parentElement)に対して scrollIntoView が 1 回呼ばれる
|
|
746
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledTimes(1);
|
|
747
|
-
expect(scrollIntoViewSpy.mock.instances[0]).toBe(container);
|
|
748
|
-
container.remove();
|
|
749
|
-
});
|
|
750
|
-
it('data-click-scroll-error がない場合は scrollIntoView が呼ばれない', async () => {
|
|
751
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('Error', {
|
|
752
|
-
status: 500,
|
|
753
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
754
|
-
})));
|
|
755
|
-
const form = document.createElement('form');
|
|
756
|
-
const btn = document.createElement('button');
|
|
757
|
-
btn.type = 'button';
|
|
758
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/no-scroll');
|
|
759
|
-
form.appendChild(btn);
|
|
760
|
-
document.body.appendChild(form);
|
|
761
|
-
await waitForDomSettled();
|
|
762
|
-
btn.click();
|
|
763
|
-
await waitForCondition(() => form.getAttribute('data-message-level') === 'error', { description: 'error message set without scroll-error attr' });
|
|
764
|
-
expect(scrollIntoViewSpy).not.toHaveBeenCalled();
|
|
765
|
-
form.remove();
|
|
766
|
-
});
|
|
767
|
-
it('バリデーション失敗: scrollIntoView が呼ばれる', async () => {
|
|
768
|
-
const fetchSpy = vi.spyOn(globalThis, 'fetch');
|
|
769
|
-
const form = document.createElement('form');
|
|
770
|
-
const input = document.createElement('input');
|
|
771
|
-
input.name = 'name';
|
|
772
|
-
input.required = true;
|
|
773
|
-
input.value = '';
|
|
774
|
-
form.appendChild(input);
|
|
775
|
-
const btn = document.createElement('button');
|
|
776
|
-
btn.type = 'button';
|
|
777
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/validate');
|
|
778
|
-
btn.setAttribute('data-click-form', '');
|
|
779
|
-
btn.setAttribute('data-click-validate', '');
|
|
780
|
-
btn.setAttribute('data-click-scroll-error', '');
|
|
781
|
-
form.appendChild(btn);
|
|
782
|
-
document.body.appendChild(form);
|
|
783
|
-
await waitForDomSettled();
|
|
784
|
-
btn.click();
|
|
785
|
-
await waitForCondition(() => scrollIntoViewSpy.mock.calls.length > 0, { description: 'scrollIntoView called on validation error' });
|
|
786
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledWith({
|
|
787
|
-
behavior: 'smooth',
|
|
788
|
-
block: 'nearest',
|
|
789
|
-
});
|
|
790
|
-
expect(fetchSpy).not.toHaveBeenCalled();
|
|
791
|
-
form.remove();
|
|
792
|
-
});
|
|
793
|
-
it('バリデーション失敗(複数 invalid): scrollIntoView は先頭の invalid 要素に 1 回だけ呼ばれ、reportValidity も先頭要素に 1 回だけ呼ばれる', async () => {
|
|
794
|
-
// 検出フェーズは checkValidity で行われるため、checkValidity をスタブして両 input を invalid にする
|
|
795
|
-
vi.spyOn(HTMLInputElement.prototype, 'checkValidity').mockReturnValue(false);
|
|
796
|
-
// reportValidity は先頭 1 要素にだけ呼ばれることを確認するためスパイを立てる
|
|
797
|
-
const reportValiditySpy = vi
|
|
798
|
-
.spyOn(HTMLInputElement.prototype, 'reportValidity')
|
|
799
|
-
.mockReturnValue(false);
|
|
800
|
-
const form = document.createElement('form');
|
|
801
|
-
const inputA = document.createElement('input'); // DOM 上で先頭
|
|
802
|
-
inputA.name = 'name';
|
|
803
|
-
form.appendChild(inputA);
|
|
804
|
-
const inputB = document.createElement('input'); // DOM 上で 2 番目
|
|
805
|
-
inputB.name = 'email';
|
|
806
|
-
form.appendChild(inputB);
|
|
807
|
-
const btn = document.createElement('button');
|
|
808
|
-
btn.type = 'button';
|
|
809
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/multi-validate');
|
|
810
|
-
btn.setAttribute('data-click-form', '');
|
|
811
|
-
btn.setAttribute('data-click-validate', '');
|
|
812
|
-
btn.setAttribute('data-click-scroll-error', '');
|
|
813
|
-
form.appendChild(btn);
|
|
814
|
-
document.body.appendChild(form);
|
|
815
|
-
await waitForDomSettled();
|
|
816
|
-
btn.click();
|
|
817
|
-
await waitForCondition(() => scrollIntoViewSpy.mock.calls.length > 0, { description: 'scrollIntoView called once on first invalid' });
|
|
818
|
-
// scrollIntoView は 1 回だけ、先頭の invalid 要素(inputA)に対して呼ばれる
|
|
819
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledTimes(1);
|
|
820
|
-
expect(scrollIntoViewSpy.mock.instances[0]).toBe(inputA);
|
|
821
|
-
// reportValidity も先頭要素にだけ呼ばれる(複数の native UI が出ない)
|
|
822
|
-
expect(reportValiditySpy).toHaveBeenCalledTimes(1);
|
|
823
|
-
expect(reportValiditySpy.mock.instances[0]).toBe(inputA);
|
|
824
|
-
form.remove();
|
|
825
|
-
});
|
|
826
|
-
});
|
|
827
|
-
// -----------------------------------------------------------------------
|
|
828
|
-
// data-click-scroll
|
|
829
|
-
// -----------------------------------------------------------------------
|
|
830
|
-
describe('data-click-scroll', () => {
|
|
831
|
-
let scrollIntoViewSpy;
|
|
832
|
-
beforeEach(() => {
|
|
833
|
-
Element.prototype.scrollIntoView = () => { };
|
|
834
|
-
scrollIntoViewSpy = vi
|
|
835
|
-
.spyOn(Element.prototype, 'scrollIntoView')
|
|
836
|
-
.mockImplementation(() => { });
|
|
837
|
-
});
|
|
838
|
-
afterEach(() => {
|
|
839
|
-
delete Element.prototype
|
|
840
|
-
.scrollIntoView;
|
|
841
|
-
});
|
|
842
|
-
it('成功時にセレクターで指定した要素へ scrollIntoView が呼ばれる', async () => {
|
|
843
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } })));
|
|
844
|
-
const target = document.createElement('div');
|
|
845
|
-
target.id = 'scroll-target';
|
|
846
|
-
document.body.appendChild(target);
|
|
847
|
-
const btn = document.createElement('button');
|
|
848
|
-
btn.type = 'button';
|
|
849
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/success');
|
|
850
|
-
btn.setAttribute('data-click-scroll', '#scroll-target');
|
|
851
|
-
document.body.appendChild(btn);
|
|
852
|
-
await waitForDomSettled();
|
|
853
|
-
btn.click();
|
|
854
|
-
await waitForCondition(() => scrollIntoViewSpy.mock.calls.length > 0, { description: 'scrollIntoView called on success' });
|
|
855
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledTimes(1);
|
|
856
|
-
expect(scrollIntoViewSpy.mock.instances[0]).toBe(target);
|
|
857
|
-
expect(scrollIntoViewSpy).toHaveBeenCalledWith({
|
|
858
|
-
behavior: 'smooth',
|
|
859
|
-
block: 'nearest',
|
|
860
|
-
});
|
|
861
|
-
target.remove();
|
|
862
|
-
btn.remove();
|
|
863
|
-
});
|
|
864
|
-
it('フェッチエラー時は scrollIntoView が呼ばれない', async () => {
|
|
865
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('Error', {
|
|
866
|
-
status: 500,
|
|
867
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
868
|
-
})));
|
|
869
|
-
const target = document.createElement('div');
|
|
870
|
-
target.id = 'scroll-target-error';
|
|
871
|
-
document.body.appendChild(target);
|
|
872
|
-
const form = document.createElement('form');
|
|
873
|
-
const btn = document.createElement('button');
|
|
874
|
-
btn.type = 'button';
|
|
875
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/fail');
|
|
876
|
-
btn.setAttribute('data-click-scroll', '#scroll-target-error');
|
|
877
|
-
form.appendChild(btn);
|
|
878
|
-
document.body.appendChild(form);
|
|
879
|
-
await waitForDomSettled();
|
|
880
|
-
btn.click();
|
|
881
|
-
await waitForCondition(() => form.getAttribute('data-message-level') === 'error', { description: 'error message set' });
|
|
882
|
-
expect(scrollIntoViewSpy).not.toHaveBeenCalled();
|
|
883
|
-
target.remove();
|
|
884
|
-
form.remove();
|
|
885
|
-
});
|
|
886
|
-
it('セレクターにマッチする要素がない場合は scrollIntoView が呼ばれない', async () => {
|
|
887
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } })));
|
|
888
|
-
const btn = document.createElement('button');
|
|
889
|
-
btn.type = 'button';
|
|
890
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/success-no-target');
|
|
891
|
-
btn.setAttribute('data-click-scroll', '#nonexistent');
|
|
892
|
-
document.body.appendChild(btn);
|
|
893
|
-
await waitForDomSettled();
|
|
894
|
-
btn.click();
|
|
895
|
-
// fetch 完了を待つ(scrollIntoView は呼ばれないのでタイムアウト前に別の条件で判断)
|
|
896
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
897
|
-
expect(scrollIntoViewSpy).not.toHaveBeenCalled();
|
|
898
|
-
btn.remove();
|
|
899
|
-
});
|
|
900
|
-
});
|
|
901
|
-
// -----------------------------------------------------------------------
|
|
902
|
-
// history.pushState
|
|
903
|
-
// -----------------------------------------------------------------------
|
|
904
|
-
it('data-click-history calls history.pushState with the specified URL', async () => {
|
|
905
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
906
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
907
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
908
|
-
});
|
|
909
|
-
const container = document.createElement('div');
|
|
910
|
-
document.body.appendChild(container);
|
|
911
|
-
const btn = document.createElement('button');
|
|
912
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/history');
|
|
913
|
-
btn.setAttribute('data-click-history', '/new-path');
|
|
914
|
-
container.appendChild(btn);
|
|
915
|
-
await waitForDomSettled();
|
|
916
|
-
btn.click();
|
|
917
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
918
|
-
description: 'pushState called',
|
|
919
|
-
});
|
|
920
|
-
expect(pushStateSpy).toHaveBeenCalledWith({ __haoriHistoryState__: true }, '', expect.stringContaining('/new-path'));
|
|
921
|
-
container.remove();
|
|
922
|
-
});
|
|
923
|
-
it('data-click-history-data appends query params to the URL', async () => {
|
|
924
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
925
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
926
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
927
|
-
});
|
|
928
|
-
const container = document.createElement('div');
|
|
929
|
-
document.body.appendChild(container);
|
|
930
|
-
const btn = document.createElement('button');
|
|
931
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/history-data');
|
|
932
|
-
btn.setAttribute('data-click-history', '/search');
|
|
933
|
-
btn.setAttribute('data-click-history-data', 'keyword=hello&page=1');
|
|
934
|
-
container.appendChild(btn);
|
|
935
|
-
await waitForDomSettled();
|
|
936
|
-
btn.click();
|
|
937
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
938
|
-
description: 'pushState called with query params',
|
|
939
|
-
});
|
|
940
|
-
expect(pushStateSpy).toHaveBeenCalledWith({ __haoriHistoryState__: true }, '', expect.stringMatching(/\/search\?.*keyword=hello/));
|
|
941
|
-
container.remove();
|
|
942
|
-
});
|
|
943
|
-
it('data-click-history omitted with data only updates query on current path', async () => {
|
|
944
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
945
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
946
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
947
|
-
});
|
|
948
|
-
const container = document.createElement('div');
|
|
949
|
-
document.body.appendChild(container);
|
|
950
|
-
const btn = document.createElement('button');
|
|
951
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/history-data-only');
|
|
952
|
-
btn.setAttribute('data-click-history-data', 'tab=list');
|
|
953
|
-
container.appendChild(btn);
|
|
954
|
-
await waitForDomSettled();
|
|
955
|
-
btn.click();
|
|
956
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
957
|
-
description: 'pushState called with current-path query update',
|
|
958
|
-
});
|
|
959
|
-
expect(pushStateSpy).toHaveBeenCalledWith({ __haoriHistoryState__: true }, '', expect.stringMatching(/tab=list/));
|
|
960
|
-
container.remove();
|
|
961
|
-
});
|
|
962
|
-
it('data-click-history-form appends form values as query params', async () => {
|
|
963
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
964
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
965
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
966
|
-
});
|
|
967
|
-
const container = document.createElement('div');
|
|
968
|
-
document.body.appendChild(container);
|
|
969
|
-
const form = document.createElement('form');
|
|
970
|
-
form.id = 'history-form';
|
|
971
|
-
const input = document.createElement('input');
|
|
972
|
-
input.name = 'q';
|
|
973
|
-
input.value = 'vitest';
|
|
974
|
-
form.appendChild(input);
|
|
975
|
-
container.appendChild(form);
|
|
976
|
-
const btn = document.createElement('button');
|
|
977
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/history-form');
|
|
978
|
-
btn.setAttribute('data-click-history', '/search');
|
|
979
|
-
btn.setAttribute('data-click-history-form', '#history-form');
|
|
980
|
-
container.appendChild(btn);
|
|
981
|
-
await waitForDomSettled();
|
|
982
|
-
btn.click();
|
|
983
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
984
|
-
description: 'pushState called with form values',
|
|
985
|
-
});
|
|
986
|
-
expect(pushStateSpy).toHaveBeenCalledWith({ __haoriHistoryState__: true }, '', expect.stringMatching(/\/search\?.*q=vitest/));
|
|
987
|
-
container.remove();
|
|
988
|
-
});
|
|
989
|
-
it('data-click-reset と data-click-history-form でリセット前の値を使わない', async () => {
|
|
990
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
991
|
-
const container = document.createElement('div');
|
|
992
|
-
document.body.appendChild(container);
|
|
993
|
-
const form = document.createElement('form');
|
|
994
|
-
form.id = 'reset-history-form';
|
|
995
|
-
const input = document.createElement('input');
|
|
996
|
-
input.name = 'q';
|
|
997
|
-
input.setAttribute('value', 'default');
|
|
998
|
-
input.value = 'dirty';
|
|
999
|
-
form.appendChild(input);
|
|
1000
|
-
container.appendChild(form);
|
|
1001
|
-
const btn = document.createElement('button');
|
|
1002
|
-
btn.setAttribute('data-click-reset', '#reset-history-form');
|
|
1003
|
-
btn.setAttribute('data-click-history', '/search');
|
|
1004
|
-
btn.setAttribute('data-click-history-form', '#reset-history-form');
|
|
1005
|
-
container.appendChild(btn);
|
|
1006
|
-
await waitForDomSettled();
|
|
1007
|
-
btn.click();
|
|
1008
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
1009
|
-
description: 'pushState called after reset processing',
|
|
1010
|
-
});
|
|
1011
|
-
expect(pushStateSpy).toHaveBeenCalledWith({ __haoriHistoryState__: true }, '', expect.stringContaining('/search'));
|
|
1012
|
-
expect(String(pushStateSpy.mock.calls[0]?.[2] ?? '')).not.toContain('dirty');
|
|
1013
|
-
expect(input.value).toBe('default');
|
|
1014
|
-
container.remove();
|
|
1015
|
-
});
|
|
1016
|
-
it('data-click-copy で history-data を pushState 前に更新する', async () => {
|
|
1017
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
1018
|
-
const container = document.createElement('div');
|
|
1019
|
-
document.body.appendChild(container);
|
|
1020
|
-
const form = document.createElement('form');
|
|
1021
|
-
form.id = 'copy-history-form';
|
|
1022
|
-
form.setAttribute('data-bind', '{"keyword":"stale"}');
|
|
1023
|
-
const input = document.createElement('input');
|
|
1024
|
-
input.name = 'keyword';
|
|
1025
|
-
input.value = 'copied';
|
|
1026
|
-
form.appendChild(input);
|
|
1027
|
-
const btn = document.createElement('button');
|
|
1028
|
-
btn.setAttribute('data-click-form', '#copy-history-form');
|
|
1029
|
-
btn.setAttribute('data-click-copy', '#copy-history-form');
|
|
1030
|
-
btn.setAttribute('data-click-history', '/search');
|
|
1031
|
-
btn.setAttribute('data-click-history-data', 'keyword={{keyword}}');
|
|
1032
|
-
form.appendChild(btn);
|
|
1033
|
-
container.appendChild(form);
|
|
1034
|
-
await waitForDomSettled();
|
|
1035
|
-
btn.click();
|
|
1036
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
1037
|
-
description: 'pushState called after copy processing',
|
|
1038
|
-
});
|
|
1039
|
-
expect(pushStateSpy).toHaveBeenCalledWith({ __haoriHistoryState__: true }, '', expect.stringContaining('/search?keyword=copied'));
|
|
1040
|
-
expect(String(pushStateSpy.mock.calls[0]?.[2] ?? '')).not.toContain('stale');
|
|
1041
|
-
container.remove();
|
|
1042
|
-
});
|
|
1043
|
-
it('data-click-reset-before で history-data と history-form は後続の bind/copy に上書きされない', async () => {
|
|
1044
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
1045
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
1046
|
-
return Promise.resolve(new Response(JSON.stringify({ keyword: 'copied' }), {
|
|
1047
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1048
|
-
}));
|
|
1049
|
-
});
|
|
1050
|
-
const container = document.createElement('div');
|
|
1051
|
-
document.body.appendChild(container);
|
|
1052
|
-
const form = document.createElement('form');
|
|
1053
|
-
form.id = 'reset-before-history-form';
|
|
1054
|
-
form.setAttribute('data-bind', '{"keyword":"reset"}');
|
|
1055
|
-
const input = document.createElement('input');
|
|
1056
|
-
input.name = 'keyword';
|
|
1057
|
-
input.value = 'dirty';
|
|
1058
|
-
form.appendChild(input);
|
|
1059
|
-
container.appendChild(form);
|
|
1060
|
-
const btn = document.createElement('button');
|
|
1061
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/history-reset-before');
|
|
1062
|
-
btn.setAttribute('data-click-fetch-method', 'POST');
|
|
1063
|
-
btn.setAttribute('data-click-reset-before', '#reset-before-history-form');
|
|
1064
|
-
btn.setAttribute('data-click-bind', '#reset-before-history-form');
|
|
1065
|
-
btn.setAttribute('data-click-history', '/search');
|
|
1066
|
-
btn.setAttribute('data-click-history-data', 'keyword={{keyword}}');
|
|
1067
|
-
btn.setAttribute('data-click-history-form', '#reset-before-history-form');
|
|
1068
|
-
btn.setAttribute('data-click-copy', '#reset-before-history-form');
|
|
1069
|
-
form.appendChild(btn);
|
|
1070
|
-
await waitForDomSettled();
|
|
1071
|
-
btn.click();
|
|
1072
|
-
await waitForCondition(() => pushStateSpy.mock.calls.length > 0, {
|
|
1073
|
-
description: 'pushState called after reset-before processing',
|
|
1074
|
-
});
|
|
1075
|
-
const call = pushStateSpy.mock.calls[0];
|
|
1076
|
-
expect(String(call[2] ?? '')).toContain('/search?');
|
|
1077
|
-
expect(String(call[2] ?? '')).toContain('keyword=reset');
|
|
1078
|
-
expect(String(call[2] ?? '')).not.toContain('copied');
|
|
1079
|
-
container.remove();
|
|
1080
|
-
});
|
|
1081
|
-
it('data-click-history with cross-origin URL logs error and skips pushState', async () => {
|
|
1082
|
-
const pushStateSpy = vi.spyOn(history, 'pushState').mockImplementation(() => { });
|
|
1083
|
-
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
1084
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
1085
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
1086
|
-
});
|
|
1087
|
-
const container = document.createElement('div');
|
|
1088
|
-
document.body.appendChild(container);
|
|
1089
|
-
const btn = document.createElement('button');
|
|
1090
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/history-cross-origin');
|
|
1091
|
-
btn.setAttribute('data-click-history', 'https://evil.example.com/path');
|
|
1092
|
-
container.appendChild(btn);
|
|
1093
|
-
await waitForDomSettled();
|
|
1094
|
-
btn.click();
|
|
1095
|
-
// redirect がないので pushState が呼ばれないことを確認するため、十分なサイクル待機する
|
|
1096
|
-
await waitForCondition(() => errorSpy.mock.calls.length > 0, {
|
|
1097
|
-
description: 'error logged for cross-origin',
|
|
1098
|
-
});
|
|
1099
|
-
expect(pushStateSpy).not.toHaveBeenCalled();
|
|
1100
|
-
errorSpy.mockRestore();
|
|
1101
|
-
container.remove();
|
|
1102
|
-
});
|
|
1103
|
-
it('data-click-history executes before data-click-redirect', async () => {
|
|
1104
|
-
const callOrder = [];
|
|
1105
|
-
vi.spyOn(history, 'pushState').mockImplementation(() => {
|
|
1106
|
-
callOrder.push('pushState');
|
|
1107
|
-
});
|
|
1108
|
-
vi.spyOn(globalThis, 'fetch').mockImplementation(() => {
|
|
1109
|
-
return Promise.resolve(new Response('{}', { headers: { 'Content-Type': 'application/json' } }));
|
|
1110
|
-
});
|
|
1111
|
-
const container = document.createElement('div');
|
|
1112
|
-
document.body.appendChild(container);
|
|
1113
|
-
const btn = document.createElement('button');
|
|
1114
|
-
btn.setAttribute('data-click-fetch', 'http://api.test/checkout');
|
|
1115
|
-
btn.setAttribute('data-click-history', '/checkout/confirm');
|
|
1116
|
-
btn.setAttribute('data-click-redirect', '#done');
|
|
1117
|
-
container.appendChild(btn);
|
|
1118
|
-
await waitForDomSettled();
|
|
1119
|
-
btn.click();
|
|
1120
|
-
await waitForCondition(() => window.location.href.includes('#done'), {
|
|
1121
|
-
description: 'redirect after pushState',
|
|
1122
|
-
});
|
|
1123
|
-
expect(callOrder).toContain('pushState');
|
|
1124
|
-
container.remove();
|
|
1125
|
-
});
|
|
1126
|
-
});
|
|
1127
|
-
describe('popstate auto-reload', () => {
|
|
1128
|
-
it('popstate イベント発火時に location.reload() を呼び出す', async () => {
|
|
1129
|
-
const mockReload = vi.fn();
|
|
1130
|
-
vi.stubGlobal('location', { reload: mockReload });
|
|
1131
|
-
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
|
|
1132
|
-
expect(mockReload).not.toHaveBeenCalled();
|
|
1133
|
-
window.dispatchEvent(new PopStateEvent('popstate', {
|
|
1134
|
-
state: { __haoriHistoryState__: true },
|
|
1135
|
-
}));
|
|
1136
|
-
expect(mockReload).toHaveBeenCalledTimes(1);
|
|
1137
|
-
vi.unstubAllGlobals();
|
|
1138
|
-
});
|
|
1139
|
-
});
|
|
1140
|
-
//# sourceMappingURL=procedure-action-operations.test.js.map
|