kempo-ui 0.3.9 → 0.3.11

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.
Files changed (25) hide show
  1. package/dist/components/HtmlEditor.js +15 -15
  2. package/docs/src/components/HtmlEditor.js +15 -15
  3. package/package.json +3 -3
  4. package/src/components/HtmlEditor.js +24 -29
  5. package/tests/components/htmlEditorControls/Align.browser-test.js +203 -0
  6. package/tests/components/htmlEditorControls/Bold.browser-test.js +127 -0
  7. package/tests/components/htmlEditorControls/BulletList.browser-test.js +127 -0
  8. package/tests/components/htmlEditorControls/CharacterCount.browser-test.js +106 -0
  9. package/tests/components/htmlEditorControls/ClearFormatting.browser-test.js +127 -0
  10. package/tests/components/htmlEditorControls/CodeBlock.browser-test.js +143 -0
  11. package/tests/components/htmlEditorControls/ControlGroup.browser-test.js +116 -0
  12. package/tests/components/htmlEditorControls/ControlSpacer.browser-test.js +69 -0
  13. package/tests/components/htmlEditorControls/CreateLink.browser-test.js +129 -0
  14. package/tests/components/htmlEditorControls/DropdownControl.browser-test.js +138 -0
  15. package/tests/components/htmlEditorControls/FormatBlock.browser-test.js +163 -0
  16. package/tests/components/htmlEditorControls/InlineCode.browser-test.js +127 -0
  17. package/tests/components/htmlEditorControls/InsertTable.browser-test.js +129 -0
  18. package/tests/components/htmlEditorControls/Italic.browser-test.js +127 -0
  19. package/tests/components/htmlEditorControls/Mode.browser-test.js +138 -0
  20. package/tests/components/htmlEditorControls/NumberList.browser-test.js +127 -0
  21. package/tests/components/htmlEditorControls/Strikethrough.browser-test.js +127 -0
  22. package/tests/components/htmlEditorControls/TextBackgroundColor.browser-test.js +136 -0
  23. package/tests/components/htmlEditorControls/TextColor.browser-test.js +136 -0
  24. package/tests/components/htmlEditorControls/Underline.browser-test.js +127 -0
  25. package/tests/components/htmlEditorControls/WordCount.browser-test.js +106 -0
@@ -0,0 +1,143 @@
1
+ import '../../../src/components/HtmlEditor.js';
2
+ import CodeBlock from '../../../src/components/htmlEditorControls/CodeBlock.js';
3
+
4
+ const createEditorWithControl = async () => {
5
+ const container = document.createElement('div');
6
+ container.innerHTML = `
7
+ <k-html-editor>
8
+ <k-hec-code-block slot="toolbar-top-left"></k-hec-code-block>
9
+ </k-html-editor>
10
+ `;
11
+ document.body.appendChild(container);
12
+ const editor = container.querySelector('k-html-editor');
13
+ await new Promise(r => editor.addEventListener('ready', r, { once: true }));
14
+ const control = container.querySelector('k-hec-code-block');
15
+ return { container, editor, control };
16
+ };
17
+
18
+ const cleanup = container => {
19
+ if(container?.parentNode) container.parentNode.removeChild(container);
20
+ };
21
+
22
+ export default {
23
+ /*
24
+ Element Creation
25
+ */
26
+ 'should create k-hec-code-block element': async ({pass, fail}) => {
27
+ const { container, control } = await createEditorWithControl();
28
+ if(!(control instanceof CodeBlock)){
29
+ cleanup(container);
30
+ return fail('Element should be instance of CodeBlock');
31
+ }
32
+ cleanup(container);
33
+ pass('CodeBlock element created correctly');
34
+ },
35
+
36
+ 'should have shadow root': async ({pass, fail}) => {
37
+ const { container, control } = await createEditorWithControl();
38
+ if(!control.shadowRoot){
39
+ cleanup(container);
40
+ return fail('CodeBlock should have shadow root');
41
+ }
42
+ cleanup(container);
43
+ pass('CodeBlock has shadow root');
44
+ },
45
+
46
+ 'should render a button': async ({pass, fail}) => {
47
+ const { container, control } = await createEditorWithControl();
48
+ await control.updateComplete;
49
+ const button = control.shadowRoot.querySelector('button');
50
+ if(!button){
51
+ cleanup(container);
52
+ return fail('CodeBlock should render a button');
53
+ }
54
+ cleanup(container);
55
+ pass('CodeBlock renders a button');
56
+ },
57
+
58
+ 'should render code_blocks icon': async ({pass, fail}) => {
59
+ const { container, control } = await createEditorWithControl();
60
+ await control.updateComplete;
61
+ const icon = control.shadowRoot.querySelector('k-icon');
62
+ if(!icon){
63
+ cleanup(container);
64
+ return fail('CodeBlock should render a k-icon');
65
+ }
66
+ if(icon.getAttribute('name') !== 'code_blocks'){
67
+ cleanup(container);
68
+ return fail(`Expected icon name "code_blocks", got "${icon.getAttribute('name')}"`);
69
+ }
70
+ cleanup(container);
71
+ pass('CodeBlock renders code_blocks icon');
72
+ },
73
+
74
+ /*
75
+ Editor Integration
76
+ */
77
+ 'should find parent editor': async ({pass, fail}) => {
78
+ const { container, editor, control } = await createEditorWithControl();
79
+ if(control.editor !== editor){
80
+ cleanup(container);
81
+ return fail('CodeBlock should find its parent k-html-editor');
82
+ }
83
+ cleanup(container);
84
+ pass('CodeBlock finds parent editor');
85
+ },
86
+
87
+ 'click should call editor.formatBlock when not in code block': async ({pass, fail}) => {
88
+ const { container, editor, control } = await createEditorWithControl();
89
+ await control.updateComplete;
90
+ let calledWith = null;
91
+ editor.isSelectionInCodeBlock = () => false;
92
+ editor.formatBlock = tag => { calledWith = tag; };
93
+ control.shadowRoot.querySelector('button').click();
94
+ if(calledWith !== 'pre'){
95
+ cleanup(container);
96
+ return fail(`Expected formatBlock("pre"), got formatBlock("${calledWith}")`);
97
+ }
98
+ cleanup(container);
99
+ pass('Click calls editor.formatBlock("pre") when not in code block');
100
+ },
101
+
102
+ 'click should call editor.formatBlock("p") when in code block': async ({pass, fail}) => {
103
+ const { container, editor, control } = await createEditorWithControl();
104
+ await control.updateComplete;
105
+ let calledWith = null;
106
+ editor.isSelectionInCodeBlock = () => true;
107
+ editor.formatBlock = tag => { calledWith = tag; };
108
+ control.shadowRoot.querySelector('button').click();
109
+ if(calledWith !== 'p'){
110
+ cleanup(container);
111
+ return fail(`Expected formatBlock("p"), got formatBlock("${calledWith}")`);
112
+ }
113
+ cleanup(container);
114
+ pass('Click calls editor.formatBlock("p") when in code block');
115
+ },
116
+
117
+ /*
118
+ Code Mode Visibility
119
+ */
120
+ 'should hide in code mode': async ({pass, fail}) => {
121
+ const { container, editor, control } = await createEditorWithControl();
122
+ editor.setMode('code');
123
+ await new Promise(r => editor.addEventListener('mode-changed', r, { once: true }));
124
+ await control.updateComplete;
125
+ if(!control.hidden){
126
+ cleanup(container);
127
+ return fail('CodeBlock should be hidden in code mode');
128
+ }
129
+ cleanup(container);
130
+ pass('CodeBlock is hidden in code mode');
131
+ },
132
+
133
+ 'should be visible in visual mode': async ({pass, fail}) => {
134
+ const { container, editor, control } = await createEditorWithControl();
135
+ await control.updateComplete;
136
+ if(control.hidden){
137
+ cleanup(container);
138
+ return fail('CodeBlock should be visible in visual mode');
139
+ }
140
+ cleanup(container);
141
+ pass('CodeBlock is visible in visual mode');
142
+ },
143
+ };
@@ -0,0 +1,116 @@
1
+ import '../../../src/components/HtmlEditor.js';
2
+ import ControlGroup from '../../../src/components/htmlEditorControls/ControlGroup.js';
3
+ import '../../../src/components/htmlEditorControls/Bold.js';
4
+ import '../../../src/components/htmlEditorControls/Italic.js';
5
+
6
+ const createEditorWithGroup = async () => {
7
+ const container = document.createElement('div');
8
+ container.innerHTML = `
9
+ <k-html-editor>
10
+ <k-hec-group slot="toolbar-top-left">
11
+ <k-hec-bold></k-hec-bold>
12
+ <k-hec-italic></k-hec-italic>
13
+ </k-hec-group>
14
+ </k-html-editor>
15
+ `;
16
+ document.body.appendChild(container);
17
+ const editor = container.querySelector('k-html-editor');
18
+ await new Promise(r => editor.addEventListener('ready', r, { once: true }));
19
+ const group = container.querySelector('k-hec-group');
20
+ return { container, editor, group };
21
+ };
22
+
23
+ const cleanup = container => {
24
+ if(container?.parentNode) container.parentNode.removeChild(container);
25
+ };
26
+
27
+ export default {
28
+ /*
29
+ Element Creation
30
+ */
31
+ 'should create k-hec-group element': async ({pass, fail}) => {
32
+ const { container, group } = await createEditorWithGroup();
33
+ if(!(group instanceof ControlGroup)){
34
+ cleanup(container);
35
+ return fail('Element should be instance of ControlGroup');
36
+ }
37
+ cleanup(container);
38
+ pass('ControlGroup element created correctly');
39
+ },
40
+
41
+ 'should have shadow root': async ({pass, fail}) => {
42
+ const { container, group } = await createEditorWithGroup();
43
+ if(!group.shadowRoot){
44
+ cleanup(container);
45
+ return fail('ControlGroup should have shadow root');
46
+ }
47
+ cleanup(container);
48
+ pass('ControlGroup has shadow root');
49
+ },
50
+
51
+ 'should render a slot': async ({pass, fail}) => {
52
+ const { container, group } = await createEditorWithGroup();
53
+ await group.updateComplete;
54
+ const slot = group.shadowRoot.querySelector('slot');
55
+ if(!slot){
56
+ cleanup(container);
57
+ return fail('ControlGroup should render a slot');
58
+ }
59
+ cleanup(container);
60
+ pass('ControlGroup renders a slot');
61
+ },
62
+
63
+ /*
64
+ Visibility
65
+ */
66
+ 'should default to not hidden': async ({pass, fail}) => {
67
+ const { container, group } = await createEditorWithGroup();
68
+ if(group.hidden){
69
+ cleanup(container);
70
+ return fail('ControlGroup should not be hidden by default');
71
+ }
72
+ cleanup(container);
73
+ pass('ControlGroup is not hidden by default');
74
+ },
75
+
76
+ 'should hide when all children are hidden': async ({pass, fail}) => {
77
+ const { container, group } = await createEditorWithGroup();
78
+ const children = Array.from(group.children);
79
+ children.forEach(child => { child.hidden = true; });
80
+ group.checkVisibility(new CustomEvent('control_visibility_change'));
81
+ if(!group.hidden){
82
+ cleanup(container);
83
+ return fail('ControlGroup should hide when all children are hidden');
84
+ }
85
+ cleanup(container);
86
+ pass('ControlGroup hides when all children are hidden');
87
+ },
88
+
89
+ 'should show when at least one child is visible': async ({pass, fail}) => {
90
+ const { container, group } = await createEditorWithGroup();
91
+ const children = Array.from(group.children);
92
+ children[0].hidden = true;
93
+ children[1].hidden = false;
94
+ group.checkVisibility(new CustomEvent('control_visibility_change'));
95
+ if(group.hidden){
96
+ cleanup(container);
97
+ return fail('ControlGroup should be visible when at least one child is visible');
98
+ }
99
+ cleanup(container);
100
+ pass('ControlGroup is visible when at least one child is visible');
101
+ },
102
+
103
+ /*
104
+ Class Attribute
105
+ */
106
+ 'should set default class attribute': async ({pass, fail}) => {
107
+ const { container, group } = await createEditorWithGroup();
108
+ const cls = group.getAttribute('class');
109
+ if(!cls || !cls.includes('b')){
110
+ cleanup(container);
111
+ return fail(`Expected default class containing "b", got "${cls}"`);
112
+ }
113
+ cleanup(container);
114
+ pass('ControlGroup sets default class attribute');
115
+ },
116
+ };
@@ -0,0 +1,69 @@
1
+ import '../../../src/components/HtmlEditor.js';
2
+ import ControlSpacer from '../../../src/components/htmlEditorControls/ControlSpacer.js';
3
+
4
+ const createEditorWithControl = async () => {
5
+ const container = document.createElement('div');
6
+ container.innerHTML = `
7
+ <k-html-editor>
8
+ <k-hec-spacer slot="toolbar-top-left"></k-hec-spacer>
9
+ </k-html-editor>
10
+ `;
11
+ document.body.appendChild(container);
12
+ const editor = container.querySelector('k-html-editor');
13
+ await new Promise(r => editor.addEventListener('ready', r, { once: true }));
14
+ const control = container.querySelector('k-hec-spacer');
15
+ return { container, editor, control };
16
+ };
17
+
18
+ const cleanup = container => {
19
+ if(container?.parentNode) container.parentNode.removeChild(container);
20
+ };
21
+
22
+ export default {
23
+ /*
24
+ Element Creation
25
+ */
26
+ 'should create k-hec-spacer element': async ({pass, fail}) => {
27
+ const { container, control } = await createEditorWithControl();
28
+ if(!(control instanceof ControlSpacer)){
29
+ cleanup(container);
30
+ return fail('Element should be instance of ControlSpacer');
31
+ }
32
+ cleanup(container);
33
+ pass('ControlSpacer element created correctly');
34
+ },
35
+
36
+ 'should have shadow root': async ({pass, fail}) => {
37
+ const { container, control } = await createEditorWithControl();
38
+ if(!control.shadowRoot){
39
+ cleanup(container);
40
+ return fail('ControlSpacer should have shadow root');
41
+ }
42
+ cleanup(container);
43
+ pass('ControlSpacer has shadow root');
44
+ },
45
+
46
+ 'should have flex: 1 style': async ({pass, fail}) => {
47
+ const { container, control } = await createEditorWithControl();
48
+ await control.updateComplete;
49
+ const style = getComputedStyle(control);
50
+ if(style.flexGrow !== '1'){
51
+ cleanup(container);
52
+ return fail(`Expected flex-grow "1", got "${style.flexGrow}"`);
53
+ }
54
+ cleanup(container);
55
+ pass('ControlSpacer has flex: 1 style');
56
+ },
57
+
58
+ 'should render no visible elements': async ({pass, fail}) => {
59
+ const { container, control } = await createEditorWithControl();
60
+ await control.updateComplete;
61
+ const buttons = control.shadowRoot.querySelectorAll('button, input, select, textarea, a');
62
+ if(buttons.length > 0){
63
+ cleanup(container);
64
+ return fail('ControlSpacer should not render interactive elements');
65
+ }
66
+ cleanup(container);
67
+ pass('ControlSpacer renders no interactive elements');
68
+ },
69
+ };
@@ -0,0 +1,129 @@
1
+ import '../../../src/components/HtmlEditor.js';
2
+ import CreateLink from '../../../src/components/htmlEditorControls/CreateLink.js';
3
+
4
+ const createEditorWithControl = async () => {
5
+ const container = document.createElement('div');
6
+ container.innerHTML = `
7
+ <k-html-editor>
8
+ <k-hec-create-link slot="toolbar-top-left"></k-hec-create-link>
9
+ </k-html-editor>
10
+ `;
11
+ document.body.appendChild(container);
12
+ const editor = container.querySelector('k-html-editor');
13
+ await new Promise(r => editor.addEventListener('ready', r, { once: true }));
14
+ const control = container.querySelector('k-hec-create-link');
15
+ return { container, editor, control };
16
+ };
17
+
18
+ const cleanup = container => {
19
+ if(container?.parentNode) container.parentNode.removeChild(container);
20
+ };
21
+
22
+ export default {
23
+ /*
24
+ Element Creation
25
+ */
26
+ 'should create k-hec-create-link element': async ({pass, fail}) => {
27
+ const { container, control } = await createEditorWithControl();
28
+ if(!(control instanceof CreateLink)){
29
+ cleanup(container);
30
+ return fail('Element should be instance of CreateLink');
31
+ }
32
+ cleanup(container);
33
+ pass('CreateLink element created correctly');
34
+ },
35
+
36
+ 'should have shadow root': async ({pass, fail}) => {
37
+ const { container, control } = await createEditorWithControl();
38
+ if(!control.shadowRoot){
39
+ cleanup(container);
40
+ return fail('CreateLink should have shadow root');
41
+ }
42
+ cleanup(container);
43
+ pass('CreateLink has shadow root');
44
+ },
45
+
46
+ 'should render a button': async ({pass, fail}) => {
47
+ const { container, control } = await createEditorWithControl();
48
+ await control.updateComplete;
49
+ const button = control.shadowRoot.querySelector('button');
50
+ if(!button){
51
+ cleanup(container);
52
+ return fail('CreateLink should render a button');
53
+ }
54
+ cleanup(container);
55
+ pass('CreateLink renders a button');
56
+ },
57
+
58
+ 'should render link icon': async ({pass, fail}) => {
59
+ const { container, control } = await createEditorWithControl();
60
+ await control.updateComplete;
61
+ const icon = control.shadowRoot.querySelector('k-icon');
62
+ if(!icon){
63
+ cleanup(container);
64
+ return fail('CreateLink should render a k-icon');
65
+ }
66
+ if(icon.getAttribute('name') !== 'link'){
67
+ cleanup(container);
68
+ return fail(`Expected icon name "link", got "${icon.getAttribute('name')}"`);
69
+ }
70
+ cleanup(container);
71
+ pass('CreateLink renders link icon');
72
+ },
73
+
74
+ /*
75
+ Editor Integration
76
+ */
77
+ 'should find parent editor': async ({pass, fail}) => {
78
+ const { container, editor, control } = await createEditorWithControl();
79
+ if(control.editor !== editor){
80
+ cleanup(container);
81
+ return fail('CreateLink should find its parent k-html-editor');
82
+ }
83
+ cleanup(container);
84
+ pass('CreateLink finds parent editor');
85
+ },
86
+
87
+ 'click should open a dialog': async ({pass, fail}) => {
88
+ const { container, editor, control } = await createEditorWithControl();
89
+ await control.updateComplete;
90
+ editor.getSelectedText = () => '';
91
+ control.shadowRoot.querySelector('button').click();
92
+ await new Promise(r => setTimeout(r, 100));
93
+ const dialog = document.querySelector('k-dialog');
94
+ if(!dialog){
95
+ cleanup(container);
96
+ return fail('Click should open a k-dialog');
97
+ }
98
+ dialog.remove();
99
+ cleanup(container);
100
+ pass('Click opens a dialog');
101
+ },
102
+
103
+ /*
104
+ Code Mode Visibility
105
+ */
106
+ 'should hide in code mode': async ({pass, fail}) => {
107
+ const { container, editor, control } = await createEditorWithControl();
108
+ editor.setMode('code');
109
+ await new Promise(r => editor.addEventListener('mode-changed', r, { once: true }));
110
+ await control.updateComplete;
111
+ if(!control.hidden){
112
+ cleanup(container);
113
+ return fail('CreateLink should be hidden in code mode');
114
+ }
115
+ cleanup(container);
116
+ pass('CreateLink is hidden in code mode');
117
+ },
118
+
119
+ 'should be visible in visual mode': async ({pass, fail}) => {
120
+ const { container, editor, control } = await createEditorWithControl();
121
+ await control.updateComplete;
122
+ if(control.hidden){
123
+ cleanup(container);
124
+ return fail('CreateLink should be visible in visual mode');
125
+ }
126
+ cleanup(container);
127
+ pass('CreateLink is visible in visual mode');
128
+ },
129
+ };
@@ -0,0 +1,138 @@
1
+ import '../../../src/components/HtmlEditor.js';
2
+ import DropdownControl from '../../../src/components/htmlEditorControls/DropdownControl.js';
3
+ import '../../../src/components/htmlEditorControls/Bold.js';
4
+
5
+ const createEditorWithControl = async () => {
6
+ const container = document.createElement('div');
7
+ container.innerHTML = `
8
+ <k-html-editor>
9
+ <k-hec-dropdown slot="toolbar-top-left">
10
+ <k-icon name="more_vert" slot="icon"></k-icon>
11
+ <k-hec-bold></k-hec-bold>
12
+ </k-hec-dropdown>
13
+ </k-html-editor>
14
+ `;
15
+ document.body.appendChild(container);
16
+ const editor = container.querySelector('k-html-editor');
17
+ await new Promise(r => editor.addEventListener('ready', r, { once: true }));
18
+ const control = container.querySelector('k-hec-dropdown');
19
+ return { container, editor, control };
20
+ };
21
+
22
+ const cleanup = container => {
23
+ if(container?.parentNode) container.parentNode.removeChild(container);
24
+ };
25
+
26
+ export default {
27
+ /*
28
+ Element Creation
29
+ */
30
+ 'should create k-hec-dropdown element': async ({pass, fail}) => {
31
+ const { container, control } = await createEditorWithControl();
32
+ if(!(control instanceof DropdownControl)){
33
+ cleanup(container);
34
+ return fail('Element should be instance of DropdownControl');
35
+ }
36
+ cleanup(container);
37
+ pass('DropdownControl element created correctly');
38
+ },
39
+
40
+ 'should have shadow root': async ({pass, fail}) => {
41
+ const { container, control } = await createEditorWithControl();
42
+ if(!control.shadowRoot){
43
+ cleanup(container);
44
+ return fail('DropdownControl should have shadow root');
45
+ }
46
+ cleanup(container);
47
+ pass('DropdownControl has shadow root');
48
+ },
49
+
50
+ 'should render a k-dropdown': async ({pass, fail}) => {
51
+ const { container, control } = await createEditorWithControl();
52
+ await control.updateComplete;
53
+ const dropdown = control.shadowRoot.querySelector('k-dropdown');
54
+ if(!dropdown){
55
+ cleanup(container);
56
+ return fail('DropdownControl should render a k-dropdown');
57
+ }
58
+ cleanup(container);
59
+ pass('DropdownControl renders a k-dropdown');
60
+ },
61
+
62
+ 'should render a trigger button': async ({pass, fail}) => {
63
+ const { container, control } = await createEditorWithControl();
64
+ await control.updateComplete;
65
+ const button = control.shadowRoot.querySelector('button[slot="trigger"]');
66
+ if(!button){
67
+ cleanup(container);
68
+ return fail('DropdownControl should render a trigger button');
69
+ }
70
+ cleanup(container);
71
+ pass('DropdownControl renders a trigger button');
72
+ },
73
+
74
+ /*
75
+ Editor Integration
76
+ */
77
+ 'should find parent editor': async ({pass, fail}) => {
78
+ const { container, editor, control } = await createEditorWithControl();
79
+ if(control.editor !== editor){
80
+ cleanup(container);
81
+ return fail('DropdownControl should find its parent k-html-editor');
82
+ }
83
+ cleanup(container);
84
+ pass('DropdownControl finds parent editor');
85
+ },
86
+
87
+ /*
88
+ Opened State
89
+ */
90
+ 'should default to closed': async ({pass, fail}) => {
91
+ const { container, control } = await createEditorWithControl();
92
+ if(control.opened){
93
+ cleanup(container);
94
+ return fail('DropdownControl should default to closed');
95
+ }
96
+ cleanup(container);
97
+ pass('DropdownControl defaults to closed');
98
+ },
99
+
100
+ 'handleToggle should toggle opened state': async ({pass, fail}) => {
101
+ const { container, control } = await createEditorWithControl();
102
+ control.handleToggle();
103
+ if(!control.opened){
104
+ cleanup(container);
105
+ return fail('handleToggle should open the dropdown');
106
+ }
107
+ control.handleToggle();
108
+ if(control.opened){
109
+ cleanup(container);
110
+ return fail('handleToggle should close the dropdown');
111
+ }
112
+ cleanup(container);
113
+ pass('handleToggle toggles opened state');
114
+ },
115
+
116
+ 'handleOpened should set opened to true': async ({pass, fail}) => {
117
+ const { container, control } = await createEditorWithControl();
118
+ control.handleOpened();
119
+ if(!control.opened){
120
+ cleanup(container);
121
+ return fail('handleOpened should set opened to true');
122
+ }
123
+ cleanup(container);
124
+ pass('handleOpened sets opened to true');
125
+ },
126
+
127
+ 'handleClosed should set opened to false': async ({pass, fail}) => {
128
+ const { container, control } = await createEditorWithControl();
129
+ control.opened = true;
130
+ control.handleClosed();
131
+ if(control.opened){
132
+ cleanup(container);
133
+ return fail('handleClosed should set opened to false');
134
+ }
135
+ cleanup(container);
136
+ pass('handleClosed sets opened to false');
137
+ },
138
+ };