@api-client/ui 0.0.11 → 0.0.13
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/.eslintrc +8 -0
- package/demo/elements/authorization/cc.ts +56 -27
- package/demo/elements/index.html +3 -0
- package/demo/elements/store/file-picker.html +15 -0
- package/demo/elements/store/file-picker.ts +134 -0
- package/demo/elements/store/index.html +19 -0
- package/demo/store/StorePlugin.js +1 -0
- package/dist/bindings/base/FileBindings.d.ts +4 -0
- package/dist/bindings/base/FileBindings.d.ts.map +1 -1
- package/dist/bindings/base/FileBindings.js +21 -1
- package/dist/bindings/base/FileBindings.js.map +1 -1
- package/dist/bindings/base/StoreBindings.d.ts +6 -17
- package/dist/bindings/base/StoreBindings.d.ts.map +1 -1
- package/dist/bindings/base/StoreBindings.js +15 -60
- package/dist/bindings/base/StoreBindings.js.map +1 -1
- package/dist/bindings/web/WebFileBindings.js +1 -1
- package/dist/bindings/web/WebFileBindings.js.map +1 -1
- package/dist/define/http/certificate-add.d.ts +9 -0
- package/dist/define/http/certificate-add.d.ts.map +1 -0
- package/dist/define/http/certificate-add.js +10 -0
- package/dist/define/http/certificate-add.js.map +1 -0
- package/dist/define/store/file-picker.d.ts +9 -0
- package/dist/define/store/file-picker.d.ts.map +1 -0
- package/dist/define/store/file-picker.js +10 -0
- package/dist/define/store/file-picker.js.map +1 -0
- package/dist/define/{files → store}/share-file.d.ts +1 -1
- package/dist/define/store/share-file.d.ts.map +1 -0
- package/dist/define/{files → store}/share-file.js +2 -2
- package/dist/define/store/share-file.js.map +1 -0
- package/dist/define/ui/ui-segmented-button-set.d.ts +1 -1
- package/dist/define/ui/ui-segmented-button-set.d.ts.map +1 -1
- package/dist/define/ui/ui-segmented-button-set.js.map +1 -1
- package/dist/elements/authorization/ui/CC.styles.d.ts.map +1 -1
- package/dist/elements/authorization/ui/CC.styles.js +4 -9
- package/dist/elements/authorization/ui/CC.styles.js.map +1 -1
- package/dist/elements/authorization/ui/CcAuthorization.d.ts +14 -29
- package/dist/elements/authorization/ui/CcAuthorization.d.ts.map +1 -1
- package/dist/elements/authorization/ui/CcAuthorization.js +67 -158
- package/dist/elements/authorization/ui/CcAuthorization.js.map +1 -1
- package/dist/elements/http/CertificateAdd.element.d.ts +91 -0
- package/dist/elements/http/CertificateAdd.element.d.ts.map +1 -0
- package/dist/elements/http/CertificateAdd.element.js +389 -0
- package/dist/elements/http/CertificateAdd.element.js.map +1 -0
- package/dist/elements/http/CertificateAdd.styles.d.ts +3 -0
- package/dist/elements/http/CertificateAdd.styles.d.ts.map +1 -0
- package/dist/elements/http/CertificateAdd.styles.js +61 -0
- package/dist/elements/http/CertificateAdd.styles.js.map +1 -0
- package/dist/elements/project/ProjectRunReport.d.ts +2 -1
- package/dist/elements/project/ProjectRunReport.d.ts.map +1 -1
- package/dist/elements/project/ProjectRunReport.js.map +1 -1
- package/dist/elements/store/FilePicker.element.d.ts +87 -0
- package/dist/elements/store/FilePicker.element.d.ts.map +1 -0
- package/dist/elements/store/FilePicker.element.js +263 -0
- package/dist/elements/store/FilePicker.element.js.map +1 -0
- package/dist/elements/store/FilePicker.styles.d.ts +3 -0
- package/dist/elements/store/FilePicker.styles.d.ts.map +1 -0
- package/dist/elements/store/FilePicker.styles.js +73 -0
- package/dist/elements/store/FilePicker.styles.js.map +1 -0
- package/dist/elements/store/FilesLib.d.ts +10 -0
- package/dist/elements/store/FilesLib.d.ts.map +1 -0
- package/dist/elements/store/FilesLib.js +38 -0
- package/dist/elements/store/FilesLib.js.map +1 -0
- package/dist/elements/{files/ShareFile.d.ts → store/ShareFile.element.d.ts} +1 -1
- package/dist/elements/store/ShareFile.element.d.ts.map +1 -0
- package/dist/elements/{files/ShareFile.js → store/ShareFile.element.js} +1 -1
- package/dist/elements/store/ShareFile.element.js.map +1 -0
- package/dist/elements/store/ShareFile.styles.d.ts.map +1 -0
- package/dist/elements/{files → store}/ShareFile.styles.js.map +1 -1
- package/dist/events/EventTypes.d.ts +7 -7
- package/dist/events/EventTypes.d.ts.map +1 -1
- package/dist/events/EventTypes.js +8 -7
- package/dist/events/EventTypes.js.map +1 -1
- package/dist/events/Events.d.ts +7 -1
- package/dist/events/Events.d.ts.map +1 -1
- package/dist/events/Events.js +2 -0
- package/dist/events/Events.js.map +1 -1
- package/dist/events/FilesystemEvents.d.ts +8 -0
- package/dist/events/FilesystemEvents.d.ts.map +1 -0
- package/dist/events/FilesystemEvents.js +59 -0
- package/dist/events/FilesystemEvents.js.map +1 -0
- package/dist/events/HttpClientEvents.d.ts +0 -2
- package/dist/events/HttpClientEvents.d.ts.map +1 -1
- package/dist/events/HttpClientEvents.js +0 -2
- package/dist/events/HttpClientEvents.js.map +1 -1
- package/dist/events/StoreEvents.d.ts +8 -1
- package/dist/events/StoreEvents.d.ts.map +1 -1
- package/dist/events/StoreEvents.js +19 -0
- package/dist/events/StoreEvents.js.map +1 -1
- package/dist/http-client/idb/Arc18DataUpgrade.d.ts +0 -8
- package/dist/http-client/idb/Arc18DataUpgrade.d.ts.map +1 -1
- package/dist/http-client/idb/Arc18DataUpgrade.js +11 -206
- package/dist/http-client/idb/Arc18DataUpgrade.js.map +1 -1
- package/dist/http-client/store/StoreBroadcast.d.ts +0 -5
- package/dist/http-client/store/StoreBroadcast.d.ts.map +1 -1
- package/dist/http-client/store/StoreBroadcast.js +0 -7
- package/dist/http-client/store/StoreBroadcast.js.map +1 -1
- package/dist/lib/files/FileUtils.d.ts +9 -0
- package/dist/lib/files/FileUtils.d.ts.map +1 -0
- package/dist/lib/files/FileUtils.js +13 -0
- package/dist/lib/files/FileUtils.js.map +1 -0
- package/dist/mixins/RouteMixin.d.ts +4 -0
- package/dist/mixins/RouteMixin.d.ts.map +1 -1
- package/dist/mixins/RouteMixin.js +1 -0
- package/dist/mixins/RouteMixin.js.map +1 -1
- package/dist/pages/ApplicationScreen.d.ts +1 -1
- package/dist/pages/ApplicationScreen.d.ts.map +1 -1
- package/dist/pages/ApplicationScreen.js +4 -2
- package/dist/pages/ApplicationScreen.js.map +1 -1
- package/dist/pages/api-client/ApiClient.screen.d.ts +4 -6
- package/dist/pages/api-client/ApiClient.screen.d.ts.map +1 -1
- package/dist/pages/api-client/ApiClient.screen.js +39 -31
- package/dist/pages/api-client/ApiClient.screen.js.map +1 -1
- package/dist/pages/api-client/ApiClient.styles.d.ts.map +1 -1
- package/dist/pages/api-client/ApiClient.styles.js +0 -12
- package/dist/pages/api-client/ApiClient.styles.js.map +1 -1
- package/dist/pages/api-client/Authenticate.screen.d.ts +1 -1
- package/dist/pages/api-client/Authenticate.screen.d.ts.map +1 -1
- package/dist/pages/api-client/Authenticate.screen.js +2 -2
- package/dist/pages/api-client/Authenticate.screen.js.map +1 -1
- package/dist/pages/api-client/pages/Files.page.d.ts +6 -35
- package/dist/pages/api-client/pages/Files.page.d.ts.map +1 -1
- package/dist/pages/api-client/pages/Files.page.js +45 -141
- package/dist/pages/api-client/pages/Files.page.js.map +1 -1
- package/dist/pages/api-client/pages/Shared.page.d.ts +1 -5
- package/dist/pages/api-client/pages/Shared.page.d.ts.map +1 -1
- package/dist/pages/api-client/pages/Shared.page.js +1 -40
- package/dist/pages/api-client/pages/Shared.page.js.map +1 -1
- package/dist/pages/demo/DemoPage.d.ts +7 -0
- package/dist/pages/demo/DemoPage.d.ts.map +1 -1
- package/dist/pages/demo/DemoPage.js +14 -0
- package/dist/pages/demo/DemoPage.js.map +1 -1
- package/dist/store/FileSystem.d.ts +90 -0
- package/dist/store/FileSystem.d.ts.map +1 -0
- package/dist/store/FileSystem.js +260 -0
- package/dist/store/FileSystem.js.map +1 -0
- package/dist/styles/global-styles.d.ts.map +1 -1
- package/dist/styles/global-styles.js +7 -0
- package/dist/styles/global-styles.js.map +1 -1
- package/dist/ui/button/SegmentedButtonsSet.d.ts +14 -0
- package/dist/ui/button/SegmentedButtonsSet.d.ts.map +1 -1
- package/dist/ui/button/SegmentedButtonsSet.js.map +1 -1
- package/dist/ui/icons/Icons.d.ts +2 -1
- package/dist/ui/icons/Icons.d.ts.map +1 -1
- package/dist/ui/icons/Icons.js +1 -0
- package/dist/ui/icons/Icons.js.map +1 -1
- package/dist/ui/list/UiDropdownList.d.ts +9 -1
- package/dist/ui/list/UiDropdownList.d.ts.map +1 -1
- package/dist/ui/list/UiDropdownList.js +39 -17
- package/dist/ui/list/UiDropdownList.js.map +1 -1
- package/dist/ui/list/UiList.d.ts +6 -1
- package/dist/ui/list/UiList.d.ts.map +1 -1
- package/dist/ui/list/UiList.js +24 -9
- package/dist/ui/list/UiList.js.map +1 -1
- package/dist/ui/notification/SnackNotifications.d.ts +1 -0
- package/dist/ui/notification/SnackNotifications.d.ts.map +1 -1
- package/dist/ui/notification/SnackNotifications.js +7 -0
- package/dist/ui/notification/SnackNotifications.js.map +1 -1
- package/dist/ui/table/DataTable.d.ts +4 -0
- package/dist/ui/table/DataTable.d.ts.map +1 -1
- package/dist/ui/table/DataTable.js +23 -1
- package/dist/ui/table/DataTable.js.map +1 -1
- package/package.json +1 -1
- package/src/bindings/base/FileBindings.ts +25 -1
- package/src/bindings/base/StoreBindings.ts +16 -73
- package/src/bindings/web/WebFileBindings.ts +1 -1
- package/src/define/http/certificate-add.ts +12 -0
- package/src/define/store/file-picker.ts +12 -0
- package/src/define/{files → store}/share-file.ts +2 -2
- package/src/define/ui/ui-segmented-button-set.ts +1 -1
- package/src/elements/authorization/ui/CC.styles.ts +4 -9
- package/src/elements/authorization/ui/CcAuthorization.ts +67 -167
- package/src/elements/http/CertificateAdd.element.ts +443 -0
- package/src/elements/http/CertificateAdd.styles.ts +61 -0
- package/src/elements/project/ProjectRunReport.ts +2 -1
- package/src/elements/store/FilePicker.element.ts +297 -0
- package/src/elements/store/FilePicker.styles.ts +73 -0
- package/src/elements/store/FilesLib.ts +32 -0
- package/src/events/EventTypes.ts +8 -7
- package/src/events/Events.ts +2 -0
- package/src/events/FilesystemEvents.ts +63 -0
- package/src/events/HttpClientEvents.ts +0 -2
- package/src/events/StoreEvents.ts +21 -1
- package/src/http-client/idb/Arc18DataUpgrade.ts +84 -84
- package/src/http-client/store/StoreBroadcast.ts +0 -8
- package/src/lib/files/FileUtils.ts +12 -0
- package/src/mixins/RouteMixin.ts +8 -1
- package/src/pages/ApplicationScreen.ts +5 -3
- package/src/pages/api-client/ApiClient.screen.ts +42 -33
- package/src/pages/api-client/ApiClient.styles.ts +0 -12
- package/src/pages/api-client/Authenticate.screen.ts +2 -2
- package/src/pages/api-client/pages/Files.page.ts +48 -164
- package/src/pages/api-client/pages/Shared.page.ts +2 -40
- package/src/pages/demo/DemoPage.ts +17 -0
- package/src/store/FileSystem.ts +325 -0
- package/src/styles/global-styles.ts +7 -0
- package/src/ui/button/SegmentedButtonsSet.ts +16 -1
- package/src/ui/icons/Icons.ts +2 -1
- package/src/ui/list/UiDropdownList.ts +44 -17
- package/src/ui/list/UiList.ts +26 -10
- package/src/ui/notification/SnackNotifications.ts +8 -0
- package/src/ui/table/DataTable.ts +29 -3
- package/test/elements/http/BodyFormdataEditorElement.test.ts +458 -454
- package/test/elements/http/BodyMultipartEditorElement.test.ts +609 -605
- package/test/elements/http/BodyRawEditorElement.test.ts +60 -56
- package/test/elements/http/CertificateAdd.test.ts +430 -0
- package/test/elements/store/FilePicker.test.ts +241 -0
- package/test/env.js +3 -0
- package/test/events/EventTypes.test.ts +0 -22
- package/test/helpers/StoreHelper.ts +390 -0
- package/test/helpers/UiMock.ts +19 -2
- package/tsconfig.eslint.json +3 -1
- package/web-test-runner.config.mjs +50 -3
- package/dist/define/files/share-file.d.ts.map +0 -1
- package/dist/define/files/share-file.js.map +0 -1
- package/dist/elements/files/ShareFile.d.ts.map +0 -1
- package/dist/elements/files/ShareFile.js.map +0 -1
- package/dist/elements/files/ShareFile.styles.d.ts.map +0 -1
- package/dist/events/http-client/models/CertificatesEvents.d.ts +0 -12
- package/dist/events/http-client/models/CertificatesEvents.d.ts.map +0 -1
- package/dist/events/http-client/models/CertificatesEvents.js +0 -18
- package/dist/events/http-client/models/CertificatesEvents.js.map +0 -1
- package/dist/http-client/idb/AuthDataModel.d.ts +0 -60
- package/dist/http-client/idb/AuthDataModel.d.ts.map +0 -1
- package/dist/http-client/idb/AuthDataModel.js +0 -150
- package/dist/http-client/idb/AuthDataModel.js.map +0 -1
- package/dist/http-client/idb/HostsModel.d.ts +0 -25
- package/dist/http-client/idb/HostsModel.d.ts.map +0 -1
- package/dist/http-client/idb/HostsModel.js +0 -82
- package/dist/http-client/idb/HostsModel.js.map +0 -1
- package/dist/http-client/idb/LegacyMockedStore.d.ts +0 -214
- package/dist/http-client/idb/LegacyMockedStore.d.ts.map +0 -1
- package/dist/http-client/idb/LegacyMockedStore.js +0 -486
- package/dist/http-client/idb/LegacyMockedStore.js.map +0 -1
- package/src/events/http-client/models/CertificatesEvents.ts +0 -23
- package/src/http-client/idb/AuthDataModel.ts +0 -175
- package/src/http-client/idb/HostsModel.ts +0 -125
- package/src/http-client/idb/LegacyMockedStore.ts +0 -544
- package/test/apic-ui.test.ts +0 -31
- /package/dist/elements/{files → store}/ShareFile.styles.d.ts +0 -0
- /package/dist/elements/{files → store}/ShareFile.styles.js +0 -0
- /package/src/elements/{files/ShareFile.ts → store/ShareFile.element.ts} +0 -0
- /package/src/elements/{files → store}/ShareFile.styles.ts +0 -0
|
@@ -6,632 +6,636 @@ import SwitchElement from '../../../src/ui/input/SwitchElement.js';
|
|
|
6
6
|
import '../../../src/define/http/http-body-multipart-editor.js'
|
|
7
7
|
import UiButton from '../../../src/ui/button/UiButton.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
describe('adding a text part', () => {
|
|
48
|
-
let element: BodyMultipartEditorElement;
|
|
49
|
-
beforeEach(async () => { element = await basicFixture(); })
|
|
50
|
-
|
|
51
|
-
it('adds a model item', () => {
|
|
52
|
-
element.addEmptyText();
|
|
53
|
-
assert.lengthOf(element.model, 1);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('does not add a model when readonly', () => {
|
|
57
|
-
element.readOnly = true;
|
|
58
|
-
element.addEmptyText();
|
|
59
|
-
assert.lengthOf(element.model, 0);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('does not add a model when disabled', () => {
|
|
63
|
-
element.disabled = true;
|
|
64
|
-
element.addEmptyText();
|
|
65
|
-
assert.lengthOf(element.model, 0);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('model item has basic schema', () => {
|
|
69
|
-
element.addEmptyText();
|
|
70
|
-
assert.deepEqual(element.model[0], {
|
|
71
|
-
name: '',
|
|
72
|
-
enabled: true,
|
|
73
|
-
value: {
|
|
74
|
-
data: '',
|
|
75
|
-
type: 'string',
|
|
76
|
-
}
|
|
9
|
+
describe('elements', () => {
|
|
10
|
+
describe('http', () => {
|
|
11
|
+
(hasFormDataSupport ? describe : describe.skip)('BodyMultipartEditor', () => {
|
|
12
|
+
async function basicFixture(): Promise<BodyMultipartEditorElement> {
|
|
13
|
+
return fixture(html`<http-body-multipart-editor allowFormInfo></http-body-multipart-editor>`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
before(async () => loadMonaco());
|
|
17
|
+
|
|
18
|
+
async function valueFixture(): Promise<BodyMultipartEditorElement> {
|
|
19
|
+
const element = await basicFixture();
|
|
20
|
+
const form = new FormData();
|
|
21
|
+
form.append('text', 'text-value');
|
|
22
|
+
form.append('blob', new Blob(['blob-value'], { type: 'text/plain' }), 'blob');
|
|
23
|
+
const file = new File(['blob-value'], 'file.txt', { type: 'text/plain' });
|
|
24
|
+
form.append('file', file, 'file.txt');
|
|
25
|
+
element.value = form;
|
|
26
|
+
await aTimeout(20);
|
|
27
|
+
return element;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('Empty state', () => {
|
|
31
|
+
let element: BodyMultipartEditorElement;
|
|
32
|
+
beforeEach(async () => { element = await basicFixture(); })
|
|
33
|
+
|
|
34
|
+
it('renders the empty message', () => {
|
|
35
|
+
const node = element.shadowRoot!.querySelector('.empty-message');
|
|
36
|
+
assert.ok(node);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders the form mime information', () => {
|
|
40
|
+
const node = element.shadowRoot!.querySelector('.form-info');
|
|
41
|
+
assert.ok(node);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('has empty model', () => {
|
|
45
|
+
assert.deepEqual(element.model, []);
|
|
46
|
+
});
|
|
77
47
|
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('adds an item with undefined model', () => {
|
|
81
|
-
element.model = undefined;
|
|
82
|
-
element.addEmptyText();
|
|
83
|
-
assert.lengthOf(element.model, 1);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('does not notify model change', async () => {
|
|
87
|
-
// there's no point of storing the model without empty values. They are not generating
|
|
88
|
-
// any editor value
|
|
89
|
-
const spy = sinon.spy();
|
|
90
|
-
element.addEventListener('change', spy);
|
|
91
|
-
element.addEmptyText();
|
|
92
|
-
assert.isFalse(spy.called);
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('adding a file part', () => {
|
|
97
|
-
let element: BodyMultipartEditorElement;
|
|
98
|
-
beforeEach(async () => { element = await basicFixture(); })
|
|
99
|
-
|
|
100
|
-
it('adds a model item on add button click', () => {
|
|
101
|
-
element.addEmptyFile();
|
|
102
|
-
assert.lengthOf(element.model, 1);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('does not add a model when readonly', () => {
|
|
106
|
-
element.readOnly = true;
|
|
107
|
-
element.addEmptyFile();
|
|
108
|
-
assert.lengthOf(element.model, 0);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('does not add a model when disabled', () => {
|
|
112
|
-
element.disabled = true;
|
|
113
|
-
element.addEmptyFile();
|
|
114
|
-
assert.lengthOf(element.model, 0);
|
|
115
|
-
});
|
|
116
48
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
49
|
+
describe('adding a text part', () => {
|
|
50
|
+
let element: BodyMultipartEditorElement;
|
|
51
|
+
beforeEach(async () => { element = await basicFixture(); })
|
|
52
|
+
|
|
53
|
+
it('adds a model item', () => {
|
|
54
|
+
element.addEmptyText();
|
|
55
|
+
assert.lengthOf(element.model, 1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('does not add a model when readonly', () => {
|
|
59
|
+
element.readOnly = true;
|
|
60
|
+
element.addEmptyText();
|
|
61
|
+
assert.lengthOf(element.model, 0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('does not add a model when disabled', () => {
|
|
65
|
+
element.disabled = true;
|
|
66
|
+
element.addEmptyText();
|
|
67
|
+
assert.lengthOf(element.model, 0);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('model item has basic schema', () => {
|
|
71
|
+
element.addEmptyText();
|
|
72
|
+
assert.deepEqual(element.model[0], {
|
|
73
|
+
name: '',
|
|
74
|
+
enabled: true,
|
|
75
|
+
value: {
|
|
76
|
+
data: '',
|
|
77
|
+
type: 'string',
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('adds an item with undefined model', () => {
|
|
83
|
+
element.model = undefined;
|
|
84
|
+
element.addEmptyText();
|
|
85
|
+
assert.lengthOf(element.model, 1);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('does not notify model change', async () => {
|
|
89
|
+
// there's no point of storing the model without empty values. They are not generating
|
|
90
|
+
// any editor value
|
|
91
|
+
const spy = sinon.spy();
|
|
92
|
+
element.addEventListener('change', spy);
|
|
93
|
+
element.addEmptyText();
|
|
94
|
+
assert.isFalse(spy.called);
|
|
95
|
+
});
|
|
126
96
|
});
|
|
127
|
-
});
|
|
128
97
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
it('generated model has restored text part property', async () => {
|
|
178
|
-
element.value = form;
|
|
179
|
-
await aTimeout(20);
|
|
180
|
-
const [item] = element.model;
|
|
181
|
-
|
|
182
|
-
assert.deepEqual(item, {
|
|
183
|
-
enabled: true,
|
|
184
|
-
name: 'text',
|
|
185
|
-
value: {
|
|
186
|
-
data: 'text-value',
|
|
187
|
-
type: 'string',
|
|
188
|
-
},
|
|
98
|
+
describe('adding a file part', () => {
|
|
99
|
+
let element: BodyMultipartEditorElement;
|
|
100
|
+
beforeEach(async () => { element = await basicFixture(); })
|
|
101
|
+
|
|
102
|
+
it('adds a model item on add button click', () => {
|
|
103
|
+
element.addEmptyFile();
|
|
104
|
+
assert.lengthOf(element.model, 1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('does not add a model when readonly', () => {
|
|
108
|
+
element.readOnly = true;
|
|
109
|
+
element.addEmptyFile();
|
|
110
|
+
assert.lengthOf(element.model, 0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('does not add a model when disabled', () => {
|
|
114
|
+
element.disabled = true;
|
|
115
|
+
element.addEmptyFile();
|
|
116
|
+
assert.lengthOf(element.model, 0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('model item has basic schema', () => {
|
|
120
|
+
element.addEmptyFile();
|
|
121
|
+
assert.deepEqual(element.model[0], {
|
|
122
|
+
name: '',
|
|
123
|
+
enabled: true,
|
|
124
|
+
value: {
|
|
125
|
+
data: [],
|
|
126
|
+
type: 'file',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('adds an item with undefined model', () => {
|
|
132
|
+
element.model = undefined;
|
|
133
|
+
element.addEmptyFile();
|
|
134
|
+
assert.lengthOf(element.model, 1);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('does not notify model change', async () => {
|
|
138
|
+
// there's no point of storing the model without empty values. They are not generating
|
|
139
|
+
// any editor value
|
|
140
|
+
const spy = sinon.spy();
|
|
141
|
+
element.addEventListener('change', spy);
|
|
142
|
+
element.addEmptyFile();
|
|
143
|
+
assert.isFalse(spy.called);
|
|
144
|
+
});
|
|
189
145
|
});
|
|
190
|
-
});
|
|
191
146
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
147
|
+
describe('#value', () => {
|
|
148
|
+
let element: BodyMultipartEditorElement;
|
|
149
|
+
let form: FormData;
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
element = await basicFixture();
|
|
152
|
+
form = new FormData();
|
|
153
|
+
form.append('text', 'text-value');
|
|
154
|
+
form.append('blob', new Blob(['*****'], { type: 'text/plain' }), 'blob');
|
|
155
|
+
const file = new File(['***'], 'file.txt', { type: 'text/plain' });
|
|
156
|
+
form.append('file', file, 'file.txt');
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// maybe it should return the same value when the model never changed?
|
|
160
|
+
// it('reads set value', () => {
|
|
161
|
+
// element.value = form;
|
|
162
|
+
// assert.isTrue(element.value === form);
|
|
163
|
+
// });
|
|
164
|
+
|
|
165
|
+
it('clears the current model when value is not set', async () => {
|
|
166
|
+
element.model = [{ name: 'a', value: 'b', enabled: true, }];
|
|
167
|
+
// @ts-ignore
|
|
168
|
+
element.value = undefined;
|
|
169
|
+
await aTimeout(20);
|
|
170
|
+
assert.deepEqual(element.model, []);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('generates the view model', async () => {
|
|
174
|
+
element.value = form;
|
|
175
|
+
await aTimeout(20);
|
|
176
|
+
assert.lengthOf(element.model, 3);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('generated model has restored text part property', async () => {
|
|
180
|
+
element.value = form;
|
|
181
|
+
await aTimeout(20);
|
|
182
|
+
const [item] = element.model;
|
|
183
|
+
|
|
184
|
+
assert.deepEqual(item, {
|
|
185
|
+
enabled: true,
|
|
186
|
+
name: 'text',
|
|
187
|
+
value: {
|
|
188
|
+
data: 'text-value',
|
|
189
|
+
type: 'string',
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('generated model has restored text blob part property', async () => {
|
|
195
|
+
element.value = form;
|
|
196
|
+
await aTimeout(20);
|
|
197
|
+
const item = element.model[1];
|
|
198
|
+
assert.deepEqual(item, {
|
|
199
|
+
enabled: true,
|
|
200
|
+
name: 'blob',
|
|
201
|
+
value: {
|
|
202
|
+
data: [42, 42, 42, 42, 42],
|
|
203
|
+
type: 'blob',
|
|
204
|
+
meta: { mime: 'text/plain' },
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('generated model has restored file part property', async () => {
|
|
210
|
+
element.value = form;
|
|
211
|
+
await aTimeout(20);
|
|
212
|
+
const item = element.model[2];
|
|
213
|
+
assert.deepEqual(item, {
|
|
214
|
+
enabled: true,
|
|
215
|
+
name: 'file',
|
|
216
|
+
value: {
|
|
217
|
+
data: [42, 42, 42],
|
|
218
|
+
type: 'file',
|
|
219
|
+
meta: { mime: 'text/plain', name: 'file.txt' },
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('updates an existing model', async () => {
|
|
225
|
+
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
226
|
+
element.value = form;
|
|
227
|
+
await aTimeout(20);
|
|
228
|
+
assert.equal(element.model[0].name, 'text');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('does not notify model change', async () => {
|
|
232
|
+
// value/model setters should not dispatch change events
|
|
233
|
+
const spy = sinon.spy();
|
|
234
|
+
element.addEventListener('change', spy);
|
|
235
|
+
element.value = form;
|
|
236
|
+
await aTimeout(20);
|
|
237
|
+
assert.isFalse(spy.called);
|
|
238
|
+
});
|
|
204
239
|
});
|
|
205
|
-
});
|
|
206
240
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
241
|
+
describe('#model', () => {
|
|
242
|
+
let element: BodyMultipartEditorElement;
|
|
243
|
+
beforeEach(async () => { element = await basicFixture(); })
|
|
244
|
+
|
|
245
|
+
it('reads set value', () => {
|
|
246
|
+
const value = [{ name: 'test', value: 'true', enabled: true }];
|
|
247
|
+
element.model = value;
|
|
248
|
+
assert.deepEqual(element.model, value);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('sets an empty array when no value', () => {
|
|
252
|
+
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
253
|
+
// @ts-ignore
|
|
254
|
+
element.model = undefined;
|
|
255
|
+
assert.deepEqual(element.model, []);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// from here all tests are related to the model processing which is
|
|
259
|
+
// an async operation so the tests focus on the function call.
|
|
260
|
+
|
|
261
|
+
it('generates a value', async () => {
|
|
262
|
+
const value = [{ name: 'test', value: 'true', enabled: true }];
|
|
263
|
+
element.model = value;
|
|
264
|
+
assert.typeOf(element.value, 'FormData');
|
|
265
|
+
const result = element.value.get('test');
|
|
266
|
+
assert.equal(result, 'true');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('clears the previously set value', async () => {
|
|
270
|
+
const form = new FormData();
|
|
271
|
+
form.append('a', 'b');
|
|
272
|
+
element.value = form;
|
|
273
|
+
await aTimeout(20);
|
|
274
|
+
// @ts-ignore
|
|
275
|
+
element.model = undefined;
|
|
276
|
+
const it = element.value.entries().next();
|
|
277
|
+
assert.isTrue(it.done);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('updates existing value', async () => {
|
|
281
|
+
element.value = new FormData();
|
|
282
|
+
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
283
|
+
const it = element.value.entries().next();
|
|
284
|
+
assert.isFalse(it.done);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('does not notify model change', async () => {
|
|
288
|
+
// value/model setters should not dispatch change events
|
|
289
|
+
const spy = sinon.spy();
|
|
290
|
+
element.addEventListener('change', spy);
|
|
291
|
+
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
292
|
+
assert.isFalse(spy.called);
|
|
293
|
+
});
|
|
219
294
|
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('updates an existing model', async () => {
|
|
223
|
-
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
224
|
-
element.value = form;
|
|
225
|
-
await aTimeout(20);
|
|
226
|
-
assert.equal(element.model[0].name, 'text');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('does not notify model change', async () => {
|
|
230
|
-
// value/model setters should not dispatch change events
|
|
231
|
-
const spy = sinon.spy();
|
|
232
|
-
element.addEventListener('change', spy);
|
|
233
|
-
element.value = form;
|
|
234
|
-
await aTimeout(20);
|
|
235
|
-
assert.isFalse(spy.called);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
describe('#model', () => {
|
|
240
|
-
let element: BodyMultipartEditorElement;
|
|
241
|
-
beforeEach(async () => { element = await basicFixture(); })
|
|
242
|
-
|
|
243
|
-
it('reads set value', () => {
|
|
244
|
-
const value = [{ name: 'test', value: 'true', enabled: true }];
|
|
245
|
-
element.model = value;
|
|
246
|
-
assert.deepEqual(element.model, value);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it('sets an empty array when no value', () => {
|
|
250
|
-
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
251
|
-
// @ts-ignore
|
|
252
|
-
element.model = undefined;
|
|
253
|
-
assert.deepEqual(element.model, []);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// from here all tests are related to the model processing which is
|
|
257
|
-
// an async operation so the tests focus on the function call.
|
|
258
|
-
|
|
259
|
-
it('generates a value', async () => {
|
|
260
|
-
const value = [{ name: 'test', value: 'true', enabled: true }];
|
|
261
|
-
element.model = value;
|
|
262
|
-
assert.typeOf(element.value, 'FormData');
|
|
263
|
-
const result = element.value.get('test');
|
|
264
|
-
assert.equal(result, 'true');
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
it('clears the previously set value', async () => {
|
|
268
|
-
const form = new FormData();
|
|
269
|
-
form.append('a', 'b');
|
|
270
|
-
element.value = form;
|
|
271
|
-
await aTimeout(20);
|
|
272
|
-
// @ts-ignore
|
|
273
|
-
element.model = undefined;
|
|
274
|
-
const it = element.value.entries().next();
|
|
275
|
-
assert.isTrue(it.done);
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('updates existing value', async () => {
|
|
279
|
-
element.value = new FormData();
|
|
280
|
-
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
281
|
-
const it = element.value.entries().next();
|
|
282
|
-
assert.isFalse(it.done);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
it('does not notify model change', async () => {
|
|
286
|
-
// value/model setters should not dispatch change events
|
|
287
|
-
const spy = sinon.spy();
|
|
288
|
-
element.addEventListener('change', spy);
|
|
289
|
-
element.model = [{ name: 'test', value: 'true', enabled: true }];
|
|
290
|
-
assert.isFalse(spy.called);
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe('values rendering', () => {
|
|
295
|
-
let element: BodyMultipartEditorElement;
|
|
296
|
-
beforeEach(async () => {
|
|
297
|
-
element = await valueFixture();
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('renders form rows for each model entry', () => {
|
|
301
|
-
const items = element.shadowRoot!.querySelectorAll('.param-row');
|
|
302
|
-
assert.lengthOf(items, 3);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
it('renders the enable switch', () => {
|
|
306
|
-
const item = element.shadowRoot!.querySelector('.param-row ui-switch');
|
|
307
|
-
assert.ok(item);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('renders the remove button', () => {
|
|
311
|
-
const item = element.shadowRoot!.querySelector('.param-row ui-icon-button');
|
|
312
|
-
assert.ok(item);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it('renders name input for all types', () => {
|
|
316
|
-
const items = element.shadowRoot!.querySelectorAll('.param-row .name-input input');
|
|
317
|
-
assert.lengthOf(items, 3);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it('renders the value input for the text part', () => {
|
|
321
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(1) .value-input input');
|
|
322
|
-
assert.ok(item);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
it('renders the type input for the text part', () => {
|
|
326
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(1) .mime-input input');
|
|
327
|
-
assert.ok(item);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('renders the value input for the text with blob part', () => {
|
|
331
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(2) .value-input');
|
|
332
|
-
assert.ok(item);
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
it('renders the type input for the text with blob part', () => {
|
|
336
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(2) .mime-input');
|
|
337
|
-
assert.ok(item);
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it('renders the choose file button for the file part', () => {
|
|
341
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(3) .file-input ui-button');
|
|
342
|
-
assert.ok(item);
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
it('renders the native input for the file part', () => {
|
|
346
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(3) .file-input input') as HTMLInputElement;
|
|
347
|
-
assert.ok(item, 'has the input');
|
|
348
|
-
assert.equal(item.type, 'file', 'the input is of file type');
|
|
349
|
-
assert.isTrue(item.hasAttribute('hidden'), 'the input is hidden');
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it('renders the native input for the selected file information', () => {
|
|
353
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(3) .file-input .file-info');
|
|
354
|
-
assert.ok(item);
|
|
355
|
-
});
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
describe('enable/disable action', () => {
|
|
359
|
-
let element: BodyMultipartEditorElement;
|
|
360
|
-
beforeEach(async () => {
|
|
361
|
-
element = await valueFixture();
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('disables the form item in the model', () => {
|
|
365
|
-
const item = element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch') as SwitchElement;
|
|
366
|
-
item.click();
|
|
367
|
-
assert.isFalse(element.model[0].enabled);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it('updates the value', () => {
|
|
371
|
-
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch')) as SwitchElement;
|
|
372
|
-
item.click();
|
|
373
|
-
const { name } = element.model[0];
|
|
374
|
-
const { value } = element;
|
|
375
|
-
assert.isFalse(value.has(name));
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it('dispatches the change event', () => {
|
|
379
|
-
const spy = sinon.spy();
|
|
380
|
-
element.addEventListener('change', spy);
|
|
381
|
-
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch')) as SwitchElement;
|
|
382
|
-
item.click();
|
|
383
|
-
assert.isTrue(spy.called);
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it('re-enabled the text part', async () => {
|
|
387
|
-
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch')) as SwitchElement;
|
|
388
|
-
item.click();
|
|
389
|
-
await nextFrame();
|
|
390
|
-
item.click();
|
|
391
|
-
const { name, enabled } = element.model[0];
|
|
392
|
-
assert.isTrue(enabled, 'the item is enabled');
|
|
393
|
-
const { value } = element;
|
|
394
|
-
assert.isTrue(value.has(name), 'the part has the value');
|
|
395
|
-
assert.typeOf(value.get(name), 'string', 'the part has the correct type');
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it('re-enabled a file part', async () => {
|
|
399
|
-
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(3) ui-switch')) as SwitchElement;
|
|
400
|
-
item.click();
|
|
401
|
-
await nextFrame();
|
|
402
|
-
item.click();
|
|
403
|
-
const { name, enabled } = element.model[2];
|
|
404
|
-
assert.isTrue(enabled, 'the item is enabled');
|
|
405
|
-
const { value } = element;
|
|
406
|
-
assert.isTrue(value.has(name), 'the part has the value');
|
|
407
|
-
assert.typeOf(value.get(name), 'file', 'the part has the correct type');
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
it('dispatches the change event when re-enabling', async () => {
|
|
411
|
-
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(3) ui-switch')) as SwitchElement;
|
|
412
|
-
const spy = sinon.spy();
|
|
413
|
-
element.addEventListener('change', spy);
|
|
414
|
-
item.click();
|
|
415
|
-
await nextFrame();
|
|
416
|
-
item.click();
|
|
417
|
-
assert.equal(spy.callCount, 2);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
describe('removing an item', () => {
|
|
422
|
-
let element: BodyMultipartEditorElement;
|
|
423
|
-
beforeEach(async () => {
|
|
424
|
-
element = await valueFixture();
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it('removes an item from the model', () => {
|
|
428
|
-
const item = { ...element.model[0] };
|
|
429
|
-
const button = (element.shadowRoot!.querySelector('.param-row .delete-button')) as UiButton;
|
|
430
|
-
button.click();
|
|
431
|
-
assert.notDeepEqual(element.model[0], item);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it('updates the value', () => {
|
|
435
|
-
const item = { ...element.model[0] };
|
|
436
|
-
const button = (element.shadowRoot!.querySelector('.param-row .delete-button')) as UiButton;
|
|
437
|
-
button.click();
|
|
438
|
-
assert.isFalse(element.value.has(item.name));
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
it('dispatches the change event', () => {
|
|
442
|
-
const spy = sinon.spy();
|
|
443
|
-
element.addEventListener('change', spy);
|
|
444
|
-
const button = (element.shadowRoot!.querySelector('.param-row .delete-button')) as UiButton;
|
|
445
|
-
button.click();
|
|
446
|
-
assert.isTrue(spy.calledOnce);
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
describe('part name change', () => {
|
|
451
|
-
let element: BodyMultipartEditorElement;
|
|
452
|
-
beforeEach(async () => {
|
|
453
|
-
element = await valueFixture();
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('changes the file part name in the model', () => {
|
|
457
|
-
const item = { ...element.model[2] };
|
|
458
|
-
const input = (element.shadowRoot!.querySelector('.param-row:nth-child(3) .name-input input')) as HTMLInputElement;
|
|
459
|
-
input.value = 'new-value-test';
|
|
460
|
-
input.dispatchEvent(new CustomEvent('change'))
|
|
461
|
-
assert.notEqual(element.model[2].name, item.name);
|
|
462
|
-
assert.equal(element.model[2].name, 'new-value-test');
|
|
463
|
-
});
|
|
464
295
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
296
|
+
describe('values rendering', () => {
|
|
297
|
+
let element: BodyMultipartEditorElement;
|
|
298
|
+
beforeEach(async () => {
|
|
299
|
+
element = await valueFixture();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('renders form rows for each model entry', () => {
|
|
303
|
+
const items = element.shadowRoot!.querySelectorAll('.param-row');
|
|
304
|
+
assert.lengthOf(items, 3);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('renders the enable switch', () => {
|
|
308
|
+
const item = element.shadowRoot!.querySelector('.param-row ui-switch');
|
|
309
|
+
assert.ok(item);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('renders the remove button', () => {
|
|
313
|
+
const item = element.shadowRoot!.querySelector('.param-row ui-icon-button');
|
|
314
|
+
assert.ok(item);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('renders name input for all types', () => {
|
|
318
|
+
const items = element.shadowRoot!.querySelectorAll('.param-row .name-input input');
|
|
319
|
+
assert.lengthOf(items, 3);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('renders the value input for the text part', () => {
|
|
323
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(1) .value-input input');
|
|
324
|
+
assert.ok(item);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('renders the type input for the text part', () => {
|
|
328
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(1) .mime-input input');
|
|
329
|
+
assert.ok(item);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('renders the value input for the text with blob part', () => {
|
|
333
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(2) .value-input');
|
|
334
|
+
assert.ok(item);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('renders the type input for the text with blob part', () => {
|
|
338
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(2) .mime-input');
|
|
339
|
+
assert.ok(item);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('renders the choose file button for the file part', () => {
|
|
343
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(3) .file-input ui-button');
|
|
344
|
+
assert.ok(item);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('renders the native input for the file part', () => {
|
|
348
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(3) .file-input input') as HTMLInputElement;
|
|
349
|
+
assert.ok(item, 'has the input');
|
|
350
|
+
assert.equal(item.type, 'file', 'the input is of file type');
|
|
351
|
+
assert.isTrue(item.hasAttribute('hidden'), 'the input is hidden');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('renders the native input for the selected file information', () => {
|
|
355
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(3) .file-input .file-info');
|
|
356
|
+
assert.ok(item);
|
|
357
|
+
});
|
|
519
358
|
});
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it('dispatches the change event', async () => {
|
|
523
|
-
const spy = sinon.spy();
|
|
524
|
-
element.addEventListener('change', spy);
|
|
525
|
-
const file = new File(['***'], 'other.txt', { type: 'text/plain' });
|
|
526
|
-
await element.updateFileValue(2, file);
|
|
527
|
-
assert.isTrue(spy.calledOnce);
|
|
528
|
-
});
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
describe('text part value change', () => {
|
|
532
|
-
let element: BodyMultipartEditorElement;
|
|
533
|
-
beforeEach(async () => {
|
|
534
|
-
element = await valueFixture();
|
|
535
|
-
});
|
|
536
|
-
|
|
537
|
-
it('changes the value in the model', async () => {
|
|
538
|
-
const input = element.shadowRoot!.querySelector('.value-input input[data-index="0"]') as HTMLInputElement;
|
|
539
|
-
input.value = 'other-value';
|
|
540
|
-
input.dispatchEvent(new Event('change'));
|
|
541
|
-
assert.deepEqual(element.model[0].value, { type: 'string', data: 'other-value' }, 'the value is updated');
|
|
542
|
-
});
|
|
543
359
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
360
|
+
describe('enable/disable action', () => {
|
|
361
|
+
let element: BodyMultipartEditorElement;
|
|
362
|
+
beforeEach(async () => {
|
|
363
|
+
element = await valueFixture();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('disables the form item in the model', () => {
|
|
367
|
+
const item = element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch') as SwitchElement;
|
|
368
|
+
item.click();
|
|
369
|
+
assert.isFalse(element.model[0].enabled);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it('updates the value', () => {
|
|
373
|
+
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch')) as SwitchElement;
|
|
374
|
+
item.click();
|
|
375
|
+
const { name } = element.model[0];
|
|
376
|
+
const { value } = element;
|
|
377
|
+
assert.isFalse(value.has(name));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('dispatches the change event', () => {
|
|
381
|
+
const spy = sinon.spy();
|
|
382
|
+
element.addEventListener('change', spy);
|
|
383
|
+
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch')) as SwitchElement;
|
|
384
|
+
item.click();
|
|
385
|
+
assert.isTrue(spy.called);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('re-enabled the text part', async () => {
|
|
389
|
+
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(1) ui-switch')) as SwitchElement;
|
|
390
|
+
item.click();
|
|
391
|
+
await nextFrame();
|
|
392
|
+
item.click();
|
|
393
|
+
const { name, enabled } = element.model[0];
|
|
394
|
+
assert.isTrue(enabled, 'the item is enabled');
|
|
395
|
+
const { value } = element;
|
|
396
|
+
assert.isTrue(value.has(name), 'the part has the value');
|
|
397
|
+
assert.typeOf(value.get(name), 'string', 'the part has the correct type');
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('re-enabled a file part', async () => {
|
|
401
|
+
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(3) ui-switch')) as SwitchElement;
|
|
402
|
+
item.click();
|
|
403
|
+
await nextFrame();
|
|
404
|
+
item.click();
|
|
405
|
+
const { name, enabled } = element.model[2];
|
|
406
|
+
assert.isTrue(enabled, 'the item is enabled');
|
|
407
|
+
const { value } = element;
|
|
408
|
+
assert.isTrue(value.has(name), 'the part has the value');
|
|
409
|
+
assert.typeOf(value.get(name), 'file', 'the part has the correct type');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('dispatches the change event when re-enabling', async () => {
|
|
413
|
+
const item = (element.shadowRoot!.querySelector('.param-row:nth-child(3) ui-switch')) as SwitchElement;
|
|
414
|
+
const spy = sinon.spy();
|
|
415
|
+
element.addEventListener('change', spy);
|
|
416
|
+
item.click();
|
|
417
|
+
await nextFrame();
|
|
418
|
+
item.click();
|
|
419
|
+
assert.equal(spy.callCount, 2);
|
|
420
|
+
});
|
|
573
421
|
});
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
it('dispatches the change event', async () => {
|
|
577
|
-
const spy = sinon.spy();
|
|
578
|
-
element.addEventListener('change', spy);
|
|
579
|
-
const input = element.shadowRoot!.querySelector('.value-input input[data-index="1"]') as HTMLInputElement;
|
|
580
|
-
input.value = '* *';
|
|
581
|
-
input.dispatchEvent(new Event('change'));
|
|
582
|
-
await aTimeout(10);
|
|
583
|
-
assert.isTrue(spy.calledOnce);
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
422
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
423
|
+
describe('removing an item', () => {
|
|
424
|
+
let element: BodyMultipartEditorElement;
|
|
425
|
+
beforeEach(async () => {
|
|
426
|
+
element = await valueFixture();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
it('removes an item from the model', () => {
|
|
430
|
+
const item = { ...element.model[0] };
|
|
431
|
+
const button = (element.shadowRoot!.querySelector('.param-row .delete-button')) as UiButton;
|
|
432
|
+
button.click();
|
|
433
|
+
assert.notDeepEqual(element.model[0], item);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('updates the value', () => {
|
|
437
|
+
const item = { ...element.model[0] };
|
|
438
|
+
const button = (element.shadowRoot!.querySelector('.param-row .delete-button')) as UiButton;
|
|
439
|
+
button.click();
|
|
440
|
+
assert.isFalse(element.value.has(item.name));
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it('dispatches the change event', () => {
|
|
444
|
+
const spy = sinon.spy();
|
|
445
|
+
element.addEventListener('change', spy);
|
|
446
|
+
const button = (element.shadowRoot!.querySelector('.param-row .delete-button')) as UiButton;
|
|
447
|
+
button.click();
|
|
448
|
+
assert.isTrue(spy.calledOnce);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
600
451
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
452
|
+
describe('part name change', () => {
|
|
453
|
+
let element: BodyMultipartEditorElement;
|
|
454
|
+
beforeEach(async () => {
|
|
455
|
+
element = await valueFixture();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it('changes the file part name in the model', () => {
|
|
459
|
+
const item = { ...element.model[2] };
|
|
460
|
+
const input = (element.shadowRoot!.querySelector('.param-row:nth-child(3) .name-input input')) as HTMLInputElement;
|
|
461
|
+
input.value = 'new-value-test';
|
|
462
|
+
input.dispatchEvent(new CustomEvent('change'))
|
|
463
|
+
assert.notEqual(element.model[2].name, item.name);
|
|
464
|
+
assert.equal(element.model[2].name, 'new-value-test');
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('updates the value for file part', () => {
|
|
468
|
+
const old = element.model[2].name;
|
|
469
|
+
const input = (element.shadowRoot!.querySelector('.param-row:nth-child(3) .name-input input')) as HTMLInputElement;
|
|
470
|
+
input.value = 'new-value-test';
|
|
471
|
+
input.dispatchEvent(new CustomEvent('change'))
|
|
472
|
+
assert.isFalse(element.value.has(old), 'old part is removed');
|
|
473
|
+
assert.isTrue(element.value.has(element.model[2].name), 'new part is added');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('changes the text part name in the model', () => {
|
|
477
|
+
const item = { ...element.model[0] };
|
|
478
|
+
const input = (element.shadowRoot!.querySelector('.param-row:nth-child(1) .name-input input')) as HTMLInputElement;
|
|
479
|
+
input.value = 'new-value-test';
|
|
480
|
+
input.dispatchEvent(new CustomEvent('change'))
|
|
481
|
+
assert.notEqual(element.model[0].name, item.name);
|
|
482
|
+
assert.equal(element.model[0].name, 'new-value-test');
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it('updates the value for text part', () => {
|
|
486
|
+
const old = element.model[0].name;
|
|
487
|
+
const input = (element.shadowRoot!.querySelector('.param-row:nth-child(1) .name-input input')) as HTMLInputElement;
|
|
488
|
+
input.value = 'new-value-test';
|
|
489
|
+
input.dispatchEvent(new CustomEvent('change'))
|
|
490
|
+
assert.isFalse(element.value.has(old), 'old part is removed');
|
|
491
|
+
assert.isTrue(element.value.has(element.model[0].name), 'new part is added');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('dispatches the change event', () => {
|
|
495
|
+
const spy = sinon.spy();
|
|
496
|
+
element.addEventListener('change', spy);
|
|
497
|
+
const input = (element.shadowRoot!.querySelector('.param-row:nth-child(3) .name-input input')) as HTMLInputElement;
|
|
498
|
+
input.value = 'new-value-test';
|
|
499
|
+
input.dispatchEvent(new CustomEvent('change'))
|
|
500
|
+
assert.isTrue(spy.calledOnce);
|
|
501
|
+
});
|
|
606
502
|
});
|
|
607
|
-
});
|
|
608
503
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
504
|
+
describe('file part value change', () => {
|
|
505
|
+
let element: BodyMultipartEditorElement;
|
|
506
|
+
beforeEach(async () => {
|
|
507
|
+
element = await valueFixture();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('changes the value in the model', async () => {
|
|
511
|
+
const file = new File(['***'], 'other.txt', { type: 'text/plain' });
|
|
512
|
+
await element.updateFileValue(2, file);
|
|
513
|
+
assert.deepEqual(element.model[2], {
|
|
514
|
+
enabled: true,
|
|
515
|
+
name: 'file',
|
|
516
|
+
value: {
|
|
517
|
+
type: 'file',
|
|
518
|
+
data: [42, 42, 42],
|
|
519
|
+
meta: { mime: 'text/plain', name: 'other.txt' }
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it('dispatches the change event', async () => {
|
|
525
|
+
const spy = sinon.spy();
|
|
526
|
+
element.addEventListener('change', spy);
|
|
527
|
+
const file = new File(['***'], 'other.txt', { type: 'text/plain' });
|
|
528
|
+
await element.updateFileValue(2, file);
|
|
529
|
+
assert.isTrue(spy.calledOnce);
|
|
530
|
+
});
|
|
622
531
|
});
|
|
623
|
-
});
|
|
624
532
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
533
|
+
describe('text part value change', () => {
|
|
534
|
+
let element: BodyMultipartEditorElement;
|
|
535
|
+
beforeEach(async () => {
|
|
536
|
+
element = await valueFixture();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('changes the value in the model', async () => {
|
|
540
|
+
const input = element.shadowRoot!.querySelector('.value-input input[data-index="0"]') as HTMLInputElement;
|
|
541
|
+
input.value = 'other-value';
|
|
542
|
+
input.dispatchEvent(new Event('change'));
|
|
543
|
+
assert.deepEqual(element.model[0].value, { type: 'string', data: 'other-value' }, 'the value is updated');
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it('dispatches the change event', async () => {
|
|
547
|
+
const spy = sinon.spy();
|
|
548
|
+
element.addEventListener('change', spy);
|
|
549
|
+
const input = element.shadowRoot!.querySelector('.value-input input[data-index="0"]') as HTMLInputElement;
|
|
550
|
+
input.value = 'other-value';
|
|
551
|
+
input.dispatchEvent(new Event('change'));
|
|
552
|
+
assert.isTrue(spy.calledOnce);
|
|
553
|
+
});
|
|
554
|
+
});
|
|
628
555
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
556
|
+
describe('blob part value change', () => {
|
|
557
|
+
let element: BodyMultipartEditorElement;
|
|
558
|
+
beforeEach(async () => {
|
|
559
|
+
element = await valueFixture();
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it('changes the value in the model', async () => {
|
|
563
|
+
const input = element.shadowRoot!.querySelector('.value-input input[data-index="1"]') as HTMLInputElement;
|
|
564
|
+
input.value = '* *';
|
|
565
|
+
input.dispatchEvent(new Event('change'));
|
|
566
|
+
await aTimeout(20);
|
|
567
|
+
|
|
568
|
+
const item = element.model[1];
|
|
569
|
+
|
|
570
|
+
assert.deepEqual(item, {
|
|
571
|
+
name: 'blob',
|
|
572
|
+
value: { type: 'blob', data: [ 42, 32, 42 ], meta: { mime: 'text/plain' } },
|
|
573
|
+
enabled: true,
|
|
574
|
+
blobText: '* *'
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('dispatches the change event', async () => {
|
|
579
|
+
const spy = sinon.spy();
|
|
580
|
+
element.addEventListener('change', spy);
|
|
581
|
+
const input = element.shadowRoot!.querySelector('.value-input input[data-index="1"]') as HTMLInputElement;
|
|
582
|
+
input.value = '* *';
|
|
583
|
+
input.dispatchEvent(new Event('change'));
|
|
584
|
+
await aTimeout(10);
|
|
585
|
+
assert.isTrue(spy.calledOnce);
|
|
586
|
+
});
|
|
587
|
+
});
|
|
632
588
|
|
|
633
|
-
|
|
634
|
-
|
|
589
|
+
describe('text part type change', () => {
|
|
590
|
+
let element: BodyMultipartEditorElement;
|
|
591
|
+
beforeEach(async () => {
|
|
592
|
+
element = await valueFixture();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('changes the existing blob value', async () => {
|
|
596
|
+
const input = element.shadowRoot!.querySelector('.mime-input input[data-index="1"]') as HTMLInputElement;
|
|
597
|
+
input.value = 'text/other';
|
|
598
|
+
input.dispatchEvent(new Event('change'));
|
|
599
|
+
|
|
600
|
+
await aTimeout(20);
|
|
601
|
+
const item = element.model[1];
|
|
602
|
+
|
|
603
|
+
assert.deepEqual(item, {
|
|
604
|
+
blobText: 'blob-value',
|
|
605
|
+
name: 'blob',
|
|
606
|
+
value: { type: 'blob', data: [ 98, 108, 111, 98, 45, 118, 97, 108, 117, 101], meta: { mime: 'text/other' } },
|
|
607
|
+
enabled: true,
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('changes the existing text value to a blob', async () => {
|
|
612
|
+
const input = element.shadowRoot!.querySelector('.mime-input input[data-index="0"]') as HTMLInputElement;
|
|
613
|
+
input.value = 'text/x';
|
|
614
|
+
input.dispatchEvent(new Event('change'));
|
|
615
|
+
|
|
616
|
+
await aTimeout(20);
|
|
617
|
+
const item = element.model[0];
|
|
618
|
+
|
|
619
|
+
assert.deepEqual(item, {
|
|
620
|
+
blobText: 'text-value',
|
|
621
|
+
name: 'text',
|
|
622
|
+
value: { type: 'blob', data: [ 116, 101, 120, 116, 45, 118, 97, 108, 117, 101], meta: { mime: 'text/x' } },
|
|
623
|
+
enabled: true,
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('dispatches the change event', async () => {
|
|
628
|
+
const spy = sinon.spy();
|
|
629
|
+
element.addEventListener('change', spy);
|
|
630
|
+
|
|
631
|
+
const input = element.shadowRoot!.querySelector('.mime-input input[data-index="0"]') as HTMLInputElement;
|
|
632
|
+
input.value = 'text/x';
|
|
633
|
+
input.dispatchEvent(new Event('change'));
|
|
634
|
+
|
|
635
|
+
await aTimeout(20);
|
|
636
|
+
assert.isTrue(spy.called);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
635
639
|
});
|
|
636
640
|
});
|
|
637
641
|
});
|