kempo-ui 0.4.4 → 0.4.6
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/dist/components/Chat.js +201 -0
- package/dist/components/CodeEditor.js +21 -2
- package/dist/components/Dropdown.js +1 -1
- package/dist/components/HtmlEditor.js +42 -3
- package/dist/components/MarkdownEditor.js +173 -0
- package/dist/components/Resize.js +12 -2
- package/dist/components/TextToSpeech.js +4 -4
- package/dist/components/VoiceSelector.js +49 -0
- package/dist/components/markdownEditorControls/Bold.js +11 -0
- package/dist/components/markdownEditorControls/BulletList.js +11 -0
- package/dist/components/markdownEditorControls/Code.js +11 -0
- package/dist/components/markdownEditorControls/FormatBlock.js +30 -0
- package/dist/components/markdownEditorControls/Heading.js +11 -0
- package/dist/components/markdownEditorControls/Image.js +93 -0
- package/dist/components/markdownEditorControls/Italic.js +11 -0
- package/dist/components/markdownEditorControls/Link.js +11 -0
- package/dist/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
- package/dist/components/markdownEditorControls/Menu.js +30 -0
- package/dist/components/markdownEditorControls/NumberedList.js +11 -0
- package/dist/components/markdownEditorControls/Quote.js +11 -0
- package/dist/components/markdownEditorControls/SpeechToText.js +16 -0
- package/dist/components/markdownEditorControls/Strikethrough.js +11 -0
- package/dist/components/markdownEditorControls/Table.js +80 -0
- package/dist/utils/marked.esm.js +1 -0
- package/dist/utils/renderMarkdown.js +1 -0
- package/dist/utils/sanitizeHtml.js +1 -0
- package/dist/utils/voice.js +1 -0
- package/docs/components/accordion.html +19 -0
- package/docs/components/aside.html +19 -0
- package/docs/components/calendar.html +19 -0
- package/docs/components/card.html +19 -0
- package/docs/components/chat.html +715 -0
- package/docs/components/code-editor.html +55 -0
- package/docs/components/color-picker.html +19 -0
- package/docs/components/combobox.html +19 -0
- package/docs/components/content-slider.html +19 -0
- package/docs/components/context.html +19 -0
- package/docs/components/dialog.html +19 -0
- package/docs/components/dropdown.html +19 -0
- package/docs/components/filter-list.html +19 -0
- package/docs/components/focus-capture.html +19 -0
- package/docs/components/html-editor.html +60 -0
- package/docs/components/hybrid-component.html +19 -0
- package/docs/components/icon.html +19 -0
- package/docs/components/import.html +19 -0
- package/docs/components/light-component.html +19 -0
- package/docs/components/markdown-editor.html +924 -0
- package/docs/components/nav-spacer.html +19 -0
- package/docs/components/nav.html +19 -0
- package/docs/components/photo-viewer.html +19 -0
- package/docs/components/progress.html +19 -0
- package/docs/components/resize.html +22 -0
- package/docs/components/rich-textarea.html +650 -0
- package/docs/components/shadow-component.html +19 -0
- package/docs/components/show-more.html +19 -0
- package/docs/components/slider.html +19 -0
- package/docs/components/sortable.html +19 -0
- package/docs/components/speech-to-text.html +19 -0
- package/docs/components/spinner.html +19 -0
- package/docs/components/split.html +19 -0
- package/docs/components/table.html +19 -0
- package/docs/components/tableControls.html +19 -0
- package/docs/components/tableCustomFields.html +19 -0
- package/docs/components/tableFetchRecords.html +19 -0
- package/docs/components/tableFieldSortHide.html +19 -0
- package/docs/components/tablePagination.html +19 -0
- package/docs/components/tablePlaceholder.html +19 -0
- package/docs/components/tableRecordEditing.html +19 -0
- package/docs/components/tableRecordFiltering.html +19 -0
- package/docs/components/tableRecordHiding.html +19 -0
- package/docs/components/tableRecordSearching.html +19 -0
- package/docs/components/tableRecordSelection.html +19 -0
- package/docs/components/tableRowControls.html +19 -0
- package/docs/components/tableServerSync.html +19 -0
- package/docs/components/tableSorting.html +19 -0
- package/docs/components/tabs.html +19 -0
- package/docs/components/tags.html +19 -0
- package/docs/components/text-to-speech.html +63 -1
- package/docs/components/theme-select.html +19 -0
- package/docs/components/theme-switcher.html +19 -0
- package/docs/components/time.html +19 -0
- package/docs/components/timestamp.html +19 -0
- package/docs/components/toast.html +19 -0
- package/docs/components/toggle.html +19 -0
- package/docs/components/tree.html +19 -0
- package/docs/components/voice-selector.html +610 -0
- package/docs/icons/done_all.svg +1 -0
- package/docs/icons/format_quote.svg +1 -0
- package/docs/icons/format_strikethrough.svg +1 -0
- package/docs/icons/send.svg +1 -0
- package/docs/icons/text_fields.svg +1 -0
- package/docs/index.html +49 -0
- package/docs/src/components/Chat.js +201 -0
- package/docs/src/components/CodeEditor.js +21 -2
- package/docs/src/components/Dropdown.js +1 -1
- package/docs/src/components/HtmlEditor.js +42 -3
- package/docs/src/components/MarkdownEditor.js +173 -0
- package/docs/src/components/Resize.js +12 -2
- package/docs/src/components/TextToSpeech.js +4 -4
- package/docs/src/components/VoiceSelector.js +49 -0
- package/docs/src/components/markdownEditorControls/Bold.js +11 -0
- package/docs/src/components/markdownEditorControls/BulletList.js +11 -0
- package/docs/src/components/markdownEditorControls/Code.js +11 -0
- package/docs/src/components/markdownEditorControls/FormatBlock.js +30 -0
- package/docs/src/components/markdownEditorControls/Heading.js +11 -0
- package/docs/src/components/markdownEditorControls/Image.js +93 -0
- package/docs/src/components/markdownEditorControls/Italic.js +11 -0
- package/docs/src/components/markdownEditorControls/Link.js +11 -0
- package/docs/src/components/markdownEditorControls/MarkdownEditorControl.js +39 -0
- package/docs/src/components/markdownEditorControls/Menu.js +30 -0
- package/docs/src/components/markdownEditorControls/NumberedList.js +11 -0
- package/docs/src/components/markdownEditorControls/Quote.js +11 -0
- package/docs/src/components/markdownEditorControls/SpeechToText.js +16 -0
- package/docs/src/components/markdownEditorControls/Strikethrough.js +11 -0
- package/docs/src/components/markdownEditorControls/Table.js +80 -0
- package/docs/src/utils/marked.esm.js +1 -0
- package/docs/src/utils/renderMarkdown.js +1 -0
- package/docs/src/utils/sanitizeHtml.js +1 -0
- package/docs/src/utils/voice.js +1 -0
- package/docs/utils/context.html +19 -0
- package/docs/utils/cookie.html +19 -0
- package/docs/utils/debounce.html +19 -0
- package/docs/utils/drag.html +19 -0
- package/docs/utils/elevation.html +23 -10
- package/docs/utils/formatTimestamp.html +19 -0
- package/docs/utils/object.html +19 -0
- package/docs/utils/propConverters.html +19 -0
- package/docs/utils/sanitizeHtml.html +555 -0
- package/docs/utils/string.html +19 -0
- package/docs/utils/theme.html +19 -0
- package/docs/utils/toTitleCase.html +19 -0
- package/docs/utils/type.html +19 -0
- package/docs/utils/voice.html +548 -0
- package/docs/utils/wait.html +19 -0
- package/docs-src/components/chat.page.html +230 -0
- package/docs-src/components/code-editor.page.html +36 -0
- package/docs-src/components/html-editor.page.html +41 -0
- package/docs-src/components/markdown-editor.page.html +439 -0
- package/docs-src/components/resize.page.html +3 -0
- package/docs-src/components/text-to-speech.page.html +44 -1
- package/docs-src/components/voice-selector.page.html +125 -0
- package/docs-src/index.page.html +30 -0
- package/docs-src/nav.fragment.html +19 -0
- package/docs-src/utils/elevation.page.html +4 -10
- package/docs-src/utils/sanitizeHtml.page.html +73 -0
- package/docs-src/utils/voice.page.html +66 -0
- package/icons/done_all.svg +1 -0
- package/icons/format_quote.svg +1 -0
- package/icons/format_strikethrough.svg +1 -0
- package/icons/send.svg +1 -0
- package/icons/text_fields.svg +1 -0
- package/llms.txt +7 -2
- package/package.json +3 -2
- package/src/components/Chat.js +435 -0
- package/src/components/CodeEditor.js +58 -2
- package/src/components/Dropdown.js +1 -1
- package/src/components/HtmlEditor.js +84 -4
- package/src/components/MarkdownEditor.js +654 -0
- package/src/components/Resize.js +17 -1
- package/src/components/TextToSpeech.js +13 -2
- package/src/components/VoiceSelector.js +271 -0
- package/src/components/markdownEditorControls/Bold.js +30 -0
- package/src/components/markdownEditorControls/BulletList.js +30 -0
- package/src/components/markdownEditorControls/Code.js +39 -0
- package/src/components/markdownEditorControls/FormatBlock.js +102 -0
- package/src/components/markdownEditorControls/Heading.js +45 -0
- package/src/components/markdownEditorControls/Image.js +250 -0
- package/src/components/markdownEditorControls/Italic.js +30 -0
- package/src/components/markdownEditorControls/Link.js +74 -0
- package/src/components/markdownEditorControls/MarkdownEditorControl.js +158 -0
- package/src/components/markdownEditorControls/Menu.js +86 -0
- package/src/components/markdownEditorControls/NumberedList.js +69 -0
- package/src/components/markdownEditorControls/Quote.js +30 -0
- package/src/components/markdownEditorControls/SpeechToText.js +65 -0
- package/src/components/markdownEditorControls/Strikethrough.js +35 -0
- package/src/components/markdownEditorControls/Table.js +164 -0
- package/src/utils/marked.esm.js +77 -0
- package/src/utils/renderMarkdown.js +29 -0
- package/src/utils/sanitizeHtml.js +137 -0
- package/src/utils/voice.js +103 -0
- package/tests/components/Chat.browser-test.js +540 -0
- package/tests/components/TextToSpeech.browser-test.js +150 -1
- package/tests/components/VoiceSelector.browser-test.js +374 -0
- package/tests/utils/sanitizeHtml.browser-test.js +179 -0
- package/tests/utils/voice.browser-test.js +128 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import sanitizeHtml from '../../src/utils/sanitizeHtml.js';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
/*
|
|
5
|
+
Basic safety
|
|
6
|
+
*/
|
|
7
|
+
'should remove <script> tags entirely': async ({pass, fail}) => {
|
|
8
|
+
const out = sanitizeHtml('Hello<script>alert(1)</script>world');
|
|
9
|
+
if(/script/i.test(out) || out.includes('alert')){
|
|
10
|
+
return fail(`Expected scripts removed, got "${out}"`);
|
|
11
|
+
}
|
|
12
|
+
if(!out.includes('Hello') || !out.includes('world')){
|
|
13
|
+
return fail(`Expected user text preserved, got "${out}"`);
|
|
14
|
+
}
|
|
15
|
+
pass('<script> removed and surrounding text preserved');
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
'should remove <style> tags entirely': async ({pass, fail}) => {
|
|
19
|
+
const out = sanitizeHtml('text<style>body{display:none}</style>more');
|
|
20
|
+
if(/style/i.test(out) || out.includes('display')){
|
|
21
|
+
return fail(`Expected style removed, got "${out}"`);
|
|
22
|
+
}
|
|
23
|
+
pass('<style> removed');
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
'should remove <iframe> tags entirely': async ({pass, fail}) => {
|
|
27
|
+
const out = sanitizeHtml('<iframe src="evil.com"></iframe>safe');
|
|
28
|
+
if(/iframe/i.test(out) || out.includes('evil')){
|
|
29
|
+
return fail(`Expected iframe removed, got "${out}"`);
|
|
30
|
+
}
|
|
31
|
+
if(!out.includes('safe')){
|
|
32
|
+
return fail(`Expected following text preserved, got "${out}"`);
|
|
33
|
+
}
|
|
34
|
+
pass('<iframe> removed');
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
'should strip event-handler attributes': async ({pass, fail}) => {
|
|
38
|
+
const out = sanitizeHtml('<p onclick="alert(1)" onmouseover="x">hi</p>');
|
|
39
|
+
if(/onclick|onmouseover/i.test(out)){
|
|
40
|
+
return fail(`Expected event handlers stripped, got "${out}"`);
|
|
41
|
+
}
|
|
42
|
+
if(!out.includes('hi')){
|
|
43
|
+
return fail(`Expected text preserved, got "${out}"`);
|
|
44
|
+
}
|
|
45
|
+
pass('Event-handler attributes stripped');
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
'should reject javascript: URLs in href': async ({pass, fail}) => {
|
|
49
|
+
const out = sanitizeHtml('<a href="javascript:alert(1)">click</a>');
|
|
50
|
+
if(/javascript:/i.test(out)){
|
|
51
|
+
return fail(`Expected javascript: scheme rejected, got "${out}"`);
|
|
52
|
+
}
|
|
53
|
+
pass('javascript: URL stripped from href');
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
'should reject data: URLs in href': async ({pass, fail}) => {
|
|
57
|
+
const out = sanitizeHtml('<a href="data:text/html,<script>alert(1)</script>">click</a>');
|
|
58
|
+
if(/data:/i.test(out)){
|
|
59
|
+
return fail(`Expected data: scheme rejected, got "${out}"`);
|
|
60
|
+
}
|
|
61
|
+
pass('data: URL stripped from href');
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
Allowed content
|
|
66
|
+
*/
|
|
67
|
+
'should preserve common formatting tags': async ({pass, fail}) => {
|
|
68
|
+
const out = sanitizeHtml('<p><b>bold</b> <i>italic</i> <u>under</u></p>');
|
|
69
|
+
if(!out.includes('<b>') || !out.includes('<i>') || !out.includes('<u>') || !out.includes('<p>')){
|
|
70
|
+
return fail(`Expected b/i/u/p preserved, got "${out}"`);
|
|
71
|
+
}
|
|
72
|
+
pass('Common formatting tags preserved');
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
'should preserve safe https links': async ({pass, fail}) => {
|
|
76
|
+
const out = sanitizeHtml('<a href="https://example.com">link</a>');
|
|
77
|
+
if(!out.includes('href="https://example.com"')){
|
|
78
|
+
return fail(`Expected https link preserved, got "${out}"`);
|
|
79
|
+
}
|
|
80
|
+
pass('https link preserved');
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
'should preserve mailto: links': async ({pass, fail}) => {
|
|
84
|
+
const out = sanitizeHtml('<a href="mailto:hi@example.com">email</a>');
|
|
85
|
+
if(!out.includes('mailto:hi@example.com')){
|
|
86
|
+
return fail(`Expected mailto preserved, got "${out}"`);
|
|
87
|
+
}
|
|
88
|
+
pass('mailto: link preserved');
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
'should preserve relative URLs': async ({pass, fail}) => {
|
|
92
|
+
const out = sanitizeHtml('<a href="/about">about</a>');
|
|
93
|
+
if(!out.includes('href="/about"')){
|
|
94
|
+
return fail(`Expected relative URL preserved, got "${out}"`);
|
|
95
|
+
}
|
|
96
|
+
pass('Relative URL preserved');
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/*
|
|
100
|
+
target=_blank rel-noopener auto-fix
|
|
101
|
+
*/
|
|
102
|
+
'should add rel="noopener noreferrer" to target="_blank" links': async ({pass, fail}) => {
|
|
103
|
+
const out = sanitizeHtml('<a href="https://example.com" target="_blank">x</a>');
|
|
104
|
+
if(!out.includes('target="_blank"') || !out.includes('rel="noopener noreferrer"')){
|
|
105
|
+
return fail(`Expected target+rel auto-fixed, got "${out}"`);
|
|
106
|
+
}
|
|
107
|
+
pass('rel="noopener noreferrer" forced on target="_blank" links');
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
Disallowed-tag fallback (preserve children)
|
|
112
|
+
*/
|
|
113
|
+
'should unwrap unknown tags but keep their text': async ({pass, fail}) => {
|
|
114
|
+
const out = sanitizeHtml('<custom>hello <b>world</b></custom>');
|
|
115
|
+
if(/custom/i.test(out)){
|
|
116
|
+
return fail(`Expected <custom> unwrapped, got "${out}"`);
|
|
117
|
+
}
|
|
118
|
+
if(!out.includes('hello') || !out.includes('<b>world</b>')){
|
|
119
|
+
return fail(`Expected children preserved, got "${out}"`);
|
|
120
|
+
}
|
|
121
|
+
pass('Disallowed tag unwrapped, children kept');
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/*
|
|
125
|
+
Comments
|
|
126
|
+
*/
|
|
127
|
+
'should remove HTML comments': async ({pass, fail}) => {
|
|
128
|
+
const out = sanitizeHtml('hi<!-- evil --><b>there</b>');
|
|
129
|
+
if(out.includes('evil')){
|
|
130
|
+
return fail(`Expected comment removed, got "${out}"`);
|
|
131
|
+
}
|
|
132
|
+
pass('HTML comments removed');
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
Edge cases
|
|
137
|
+
*/
|
|
138
|
+
'should handle null/undefined/empty input': async ({pass, fail}) => {
|
|
139
|
+
if(sanitizeHtml(null) !== '') return fail('null should produce ""');
|
|
140
|
+
if(sanitizeHtml(undefined) !== '') return fail('undefined should produce ""');
|
|
141
|
+
if(sanitizeHtml('') !== '') return fail('empty string should produce ""');
|
|
142
|
+
pass('null/undefined/empty all produce ""');
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
'should preserve plain text': async ({pass, fail}) => {
|
|
146
|
+
const out = sanitizeHtml('just some text');
|
|
147
|
+
if(out !== 'just some text'){
|
|
148
|
+
return fail(`Expected pass-through plain text, got "${out}"`);
|
|
149
|
+
}
|
|
150
|
+
pass('Plain text untouched');
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
'should keep data-* attributes': async ({pass, fail}) => {
|
|
154
|
+
const out = sanitizeHtml('<span data-id="42">x</span>');
|
|
155
|
+
if(!out.includes('data-id="42"')){
|
|
156
|
+
return fail(`Expected data-id preserved, got "${out}"`);
|
|
157
|
+
}
|
|
158
|
+
pass('data-* attributes preserved');
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
/*
|
|
162
|
+
Custom whitelist
|
|
163
|
+
*/
|
|
164
|
+
'should respect a custom allowedTags whitelist': async ({pass, fail}) => {
|
|
165
|
+
const out = sanitizeHtml('<b>bold</b><i>italic</i>', {
|
|
166
|
+
allowedTags: new Set(['B'])
|
|
167
|
+
});
|
|
168
|
+
if(!out.includes('<b>bold</b>')){
|
|
169
|
+
return fail(`Expected <b> kept, got "${out}"`);
|
|
170
|
+
}
|
|
171
|
+
if(out.includes('<i>')){
|
|
172
|
+
return fail(`Expected <i> stripped, got "${out}"`);
|
|
173
|
+
}
|
|
174
|
+
if(!out.includes('italic')){
|
|
175
|
+
return fail(`Expected italic text preserved (unwrapped), got "${out}"`);
|
|
176
|
+
}
|
|
177
|
+
pass('Custom whitelist honored');
|
|
178
|
+
}
|
|
179
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import voice, {
|
|
2
|
+
getVoice,
|
|
3
|
+
setVoice,
|
|
4
|
+
subscribeToVoice
|
|
5
|
+
} from '../../src/utils/voice.js';
|
|
6
|
+
|
|
7
|
+
const STORAGE_KEY = 'k-voice';
|
|
8
|
+
|
|
9
|
+
const reset = () => {
|
|
10
|
+
try {
|
|
11
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
12
|
+
} catch(e) {}
|
|
13
|
+
setVoice('');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
/*
|
|
18
|
+
Get/Set
|
|
19
|
+
*/
|
|
20
|
+
'should default to empty string when nothing stored': async ({pass, fail}) => {
|
|
21
|
+
reset();
|
|
22
|
+
if(getVoice() !== ''){
|
|
23
|
+
return fail(`Expected '' default, got "${getVoice()}"`);
|
|
24
|
+
}
|
|
25
|
+
pass('Default voice is empty string');
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
'should set and get a voice name': async ({pass, fail}) => {
|
|
29
|
+
reset();
|
|
30
|
+
setVoice('Samantha');
|
|
31
|
+
if(getVoice() !== 'Samantha'){
|
|
32
|
+
reset();
|
|
33
|
+
return fail(`Expected "Samantha", got "${getVoice()}"`);
|
|
34
|
+
}
|
|
35
|
+
reset();
|
|
36
|
+
pass('Set/get round-trips a voice name');
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
'should persist voice to localStorage on set': async ({pass, fail}) => {
|
|
40
|
+
reset();
|
|
41
|
+
setVoice('Karen');
|
|
42
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
43
|
+
if(stored !== 'Karen'){
|
|
44
|
+
reset();
|
|
45
|
+
return fail(`Expected localStorage "${STORAGE_KEY}" = "Karen", got "${stored}"`);
|
|
46
|
+
}
|
|
47
|
+
reset();
|
|
48
|
+
pass('setVoice persists to localStorage');
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
'should clear localStorage when set to empty': async ({pass, fail}) => {
|
|
52
|
+
reset();
|
|
53
|
+
setVoice('Karen');
|
|
54
|
+
setVoice('');
|
|
55
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
56
|
+
if(stored !== null){
|
|
57
|
+
reset();
|
|
58
|
+
return fail(`Expected localStorage cleared, got "${stored}"`);
|
|
59
|
+
}
|
|
60
|
+
reset();
|
|
61
|
+
pass('Empty setVoice clears localStorage');
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/*
|
|
65
|
+
Subscribe
|
|
66
|
+
*/
|
|
67
|
+
'should fire subscriber immediately with current value': async ({pass, fail}) => {
|
|
68
|
+
reset();
|
|
69
|
+
setVoice('Daniel');
|
|
70
|
+
let received = '__none__';
|
|
71
|
+
const off = subscribeToVoice(name => { received = name; });
|
|
72
|
+
off();
|
|
73
|
+
if(received !== 'Daniel'){
|
|
74
|
+
reset();
|
|
75
|
+
return fail(`Subscriber should fire immediately with "Daniel", got "${received}"`);
|
|
76
|
+
}
|
|
77
|
+
reset();
|
|
78
|
+
pass('Subscriber fires immediately with current value');
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
'should fire subscribers when voice changes': async ({pass, fail}) => {
|
|
82
|
+
reset();
|
|
83
|
+
const events = [];
|
|
84
|
+
const off = subscribeToVoice(name => { events.push(name); });
|
|
85
|
+
setVoice('Alex');
|
|
86
|
+
setVoice('Karen');
|
|
87
|
+
off();
|
|
88
|
+
// First event is the initial fire (empty), then the two changes
|
|
89
|
+
if(events[events.length - 2] !== 'Alex' || events[events.length - 1] !== 'Karen'){
|
|
90
|
+
reset();
|
|
91
|
+
return fail(`Expected last two events "Alex", "Karen", got ${JSON.stringify(events)}`);
|
|
92
|
+
}
|
|
93
|
+
reset();
|
|
94
|
+
pass('Subscribers fire on every change');
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
'should return an unsubscribe function': async ({pass, fail}) => {
|
|
98
|
+
reset();
|
|
99
|
+
let count = 0;
|
|
100
|
+
const off = subscribeToVoice(() => { count++; });
|
|
101
|
+
const initial = count;
|
|
102
|
+
off();
|
|
103
|
+
setVoice('Alex');
|
|
104
|
+
if(count !== initial){
|
|
105
|
+
reset();
|
|
106
|
+
return fail(`Subscriber should not fire after unsubscribe (initial=${initial}, after=${count})`);
|
|
107
|
+
}
|
|
108
|
+
reset();
|
|
109
|
+
pass('Unsubscribe stops further callbacks');
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
Default export
|
|
114
|
+
*/
|
|
115
|
+
'default export exposes get/set/subscribe': async ({pass, fail}) => {
|
|
116
|
+
reset();
|
|
117
|
+
if(typeof voice.get !== 'function' || typeof voice.set !== 'function' || typeof voice.subscribe !== 'function'){
|
|
118
|
+
return fail('Default export missing one of get/set/subscribe');
|
|
119
|
+
}
|
|
120
|
+
voice.set('Tessa');
|
|
121
|
+
if(voice.get() !== 'Tessa'){
|
|
122
|
+
reset();
|
|
123
|
+
return fail(`Default export get/set roundtrip failed, got "${voice.get()}"`);
|
|
124
|
+
}
|
|
125
|
+
reset();
|
|
126
|
+
pass('Default export proxies get/set/subscribe');
|
|
127
|
+
}
|
|
128
|
+
};
|