Skip to content

Commit 1f5dfaf

Browse files
authored
Merge pull request #174 from grapoza/code-coverage-2-1-1
Improve code coverage
2 parents 9f5143f + 5e2ae53 commit 1f5dfaf

File tree

6 files changed

+278
-25
lines changed

6 files changed

+278
-25
lines changed

src/components/TreeViewNode.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -370,11 +370,8 @@
370370
* Normalizes the data model to the format consumable by TreeViewNode.
371371
*/
372372
$_treeViewNode_normalizeNodeData() {
373-
// The target model must have a treeNodeSpec property to assign defaults into
374-
if (!isProbablyObject(this.model.treeNodeSpec)) {
375-
this.$set(this.model, 'treeNodeSpec', {});
376-
}
377-
373+
// The target model must have a treeNodeSpec property to assign defaults into; if missing,
374+
// it will be normalized into existence in $_treeViewNodeAria_normalizeNodeData().
378375
this.$_treeViewNode_assignDefaultProps(this.modelDefaults, this.model.treeNodeSpec);
379376
380377
// Set expected properties if not provided

src/mixins/TreeViewDragAndDrop.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ export default {
2121
$_treeViewDnd_drop(eventData) {
2222
let node = eventData.droppedModel;
2323

24-
if (!node) {
25-
return;
26-
}
27-
2824
if (eventData.isSameTree) {
2925
// When dropping within the same tree, move/copy the actual node data.
3026

@@ -88,7 +84,7 @@ function resolveNodeIdConflicts(data, treeId) {
8884

8985
let idProp = data.treeNodeSpec.idProperty;
9086
let nodeId = data[idProp];
91-
let children = data[data.treeNodeSpec.childrenProperty] || [];
87+
let children = data[data.treeNodeSpec.childrenProperty];
9288

9389
// Copy and move need to set a new, unique Node ID.
9490
// This is a brute force test to find one that isn't in use.

tests/unit/TreeViewAria.spec.js

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ describe('TreeView.vue (ARIA)', () => {
2525

2626
let wrapper = null;
2727

28+
beforeEach(() => {
29+
jest.spyOn(console, 'error').mockImplementation(() => { });
30+
});
31+
2832
afterEach(() => {
33+
jest.restoreAllMocks();
2934
wrapper.vm.$destroy();
3035
wrapper = null;
3136
});
@@ -64,7 +69,7 @@ describe('TreeView.vue (ARIA)', () => {
6469
});
6570
});
6671

67-
describe('and customizations are provided', () => {
72+
describe('and valid customizations are provided', () => {
6873

6974
const customKeyMap = {
7075
activateItem: [1],
@@ -91,6 +96,25 @@ describe('TreeView.vue (ARIA)', () => {
9196
expect(keyMap).to.deep.equal(customKeyMap);
9297
});
9398
});
99+
100+
describe('and invalid non-array customizations are provided', () => {
101+
102+
const customKeyMap = {
103+
activateItem: 1
104+
};
105+
106+
beforeEach(() => {
107+
wrapper = createWrapper({
108+
initialModel: [],
109+
customAriaKeyMap: customKeyMap
110+
});
111+
});
112+
113+
it('should log an error', () => {
114+
expect(console.error.mock.calls[0][0])
115+
.to.equal('customAriaKeyMap properties must be Arrays of numbers (corresponding to keyCodes); property \'activateItem\' fails check.');
116+
});
117+
});
94118
});
95119

96120
describe('when created without a focusable node specified', () => {
@@ -194,6 +218,13 @@ describe('TreeView.vue (ARIA)', () => {
194218

195219
expect(wrapper.vm.model[2].treeNodeSpec.focusable).to.be.true;
196220
});
221+
222+
it('should focus the deepest last node', () => {
223+
wrapper = createWrapper({ initialModel: generateNodes(['ecsf', 'eCs', 'Ecs', ['ecs']]) });
224+
wrapper.vm.$_treeViewAria_focusLastNode();
225+
226+
expect(wrapper.vm.model[2].children[0].treeNodeSpec.focusable).to.be.true;
227+
});
197228
});
198229

199230
describe('when a node is getting deleted', () => {
@@ -337,4 +368,26 @@ describe('TreeView.vue (ARIA)', () => {
337368
expect(wrapper.vm.model[0].children[0].treeNodeSpec.state.selected).to.be.false;
338369
});
339370
});
371+
372+
describe('when selectionMode is Single', () => {
373+
374+
beforeEach(async () => {
375+
wrapper = createWrapper({ initialModel: generateNodes(['ecS', 'ecs']), selectionMode: SelectionMode.Single });
376+
});
377+
378+
describe('and a node is already selected', () => {
379+
380+
describe('and a new node is selected', () => {
381+
382+
beforeEach(async () => {
383+
wrapper.vm.model[1].treeNodeSpec.state.selected = true;
384+
await wrapper.vm.$nextTick();
385+
});
386+
387+
it('should deselect the previously selected node', () => {
388+
expect(wrapper.vm.model[0].treeNodeSpec.state.selected).to.be.false;
389+
});
390+
});
391+
});
392+
});
340393
});

tests/unit/TreeViewDragAndDrop.spec.js

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const getDefaultPropsData = function () {
1616
let elem;
1717

1818
function createWrapper(customPropsData, customAttrs) {
19-
// https://vue-test-utils.vuejs.org/api/options.html#attachtodocument
2019
elem = document.createElement('div');
2120
if (document.body) {
2221
document.body.appendChild(elem);
@@ -218,6 +217,38 @@ describe('TreeView.vue (Drag and Drop)', () => {
218217
expect(tree2.vm.model.length).to.equal(4);
219218
});
220219
});
220+
221+
describe('and node IDs in the dragged data conflict with target tree node IDs', () => {
222+
223+
let tree2;
224+
225+
beforeEach(async () => {
226+
let tree2Data = getDefaultPropsData();
227+
228+
wrapper = createWrapper();
229+
tree2 = createWrapper(tree2Data, { id: 'grtv-2' });
230+
231+
// Await here so tree2's ID can trickle down to the nodes' computeds
232+
await tree2.vm.$nextTick();
233+
234+
let startingNode = wrapper.find('#grtv-1-n2 .tree-view-node-self');
235+
startingNode.trigger('dragstart', eventData);
236+
237+
let endingNode = tree2.find('#grtv-2-n0 .tree-view-node-self-prev-target');
238+
endingNode.trigger('drop', eventData);
239+
240+
startingNode.trigger('dragend', eventData);
241+
});
242+
243+
afterEach(() => {
244+
tree2.vm.$destroy();
245+
});
246+
247+
it('should assign new IDs to the conflicting nodes', () => {
248+
expect(tree2.vm.model[0].id).to.equal('n2-1');
249+
expect(tree2.vm.model[0].children[0].id).to.equal('n2n0-1');
250+
});
251+
});
221252
});
222253

223254
describe('when a child node has emitted treeViewNodeDragMove', () => {

tests/unit/TreeViewNode.spec.js

Lines changed: 129 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,22 +118,147 @@ describe('TreeViewNode.vue', () => {
118118

119119
it('should have a title attribute on the node\'s text', () => {
120120
let elem = wrapper.find(`.tree-view-node-self-text`).element;
121-
expect(elem.getAttribute("title")).to.equal("My Title");
121+
expect(elem.getAttribute('title')).to.equal('My Title');
122122
});
123123
});
124124

125125
describe('when given a title in the model data for an input node', () => {
126126

127127
beforeEach(() => {
128128
let data = getDefaultPropsData();
129-
data.initialModel.treeNodeSpec.title = "My Title";
129+
data.initialModel.treeNodeSpec.title = 'My Title';
130130

131131
wrapper = createWrapper(data);
132132
});
133133

134134
it('should have a title attribute on the node\'s label', () => {
135135
let elem = wrapper.find(`.tree-view-node-self-label`).element;
136-
expect(elem.getAttribute("title")).to.equal("My Title");
136+
expect(elem.getAttribute('title')).to.equal('My Title');
137+
});
138+
});
139+
140+
describe('when given a name in the model data for an input node', () => {
141+
142+
describe('and it is a non-radio button input', () => {
143+
144+
describe('and that name is not a string', () => {
145+
146+
beforeEach(() => {
147+
let data = getDefaultPropsData();
148+
data.initialModel.treeNodeSpec.input.name = 42;
149+
wrapper = createWrapper(data);
150+
});
151+
152+
it('should set the name to null', () => {
153+
expect(wrapper.vm.model.treeNodeSpec.input.name).to.be.null;
154+
});
155+
});
156+
157+
describe('and that trimmed name is an empty string', () => {
158+
159+
beforeEach(() => {
160+
let data = getDefaultPropsData();
161+
data.initialModel.treeNodeSpec.input.name = ' ';
162+
wrapper = createWrapper(data);
163+
});
164+
165+
it('should set the name to null', () => {
166+
expect(wrapper.vm.model.treeNodeSpec.input.name).to.be.null;
167+
});
168+
});
169+
});
170+
171+
describe('and it is a radio button input', () => {
172+
173+
let data;
174+
175+
beforeEach(() => {
176+
data = getDefaultPropsData();
177+
data.initialModel = generateNodes(['r'])[0];
178+
});
179+
180+
describe('and that name is not a string', () => {
181+
182+
beforeEach(() => {
183+
data.initialModel.treeNodeSpec.input.name = 42;
184+
wrapper = createWrapper(data);
185+
});
186+
187+
it('should set the name to unspecifiedRadioName', () => {
188+
expect(wrapper.vm.model.treeNodeSpec.input.name).to.equal('unspecifiedRadioName');
189+
});
190+
});
191+
192+
describe('and that trimmed name is an empty string', () => {
193+
194+
beforeEach(() => {
195+
data.initialModel.treeNodeSpec.input.name = ' ';
196+
wrapper = createWrapper(data);
197+
});
198+
199+
it('should set the name to null', () => {
200+
expect(wrapper.vm.model.treeNodeSpec.input.name).to.equal('unspecifiedRadioName');
201+
});
202+
});
203+
});
204+
});
205+
206+
describe('when given a value in the model data for an input node', () => {
207+
208+
describe('and it is a radio button input', () => {
209+
210+
let data;
211+
212+
beforeEach(() => {
213+
data = getDefaultPropsData();
214+
data.initialModel = generateNodes(['r'])[0];
215+
data.initialModel.label = 'A \'Label\' & <Thing>/ "Stuff"';
216+
});
217+
218+
describe('and that value is not a string', () => {
219+
220+
beforeEach(() => {
221+
data.initialModel.treeNodeSpec.input.value = 42;
222+
wrapper = createWrapper(data);
223+
});
224+
225+
it('should set the value to the label value, minus disallowed characters', () => {
226+
expect(wrapper.vm.model.treeNodeSpec.input.value).to.equal('ALabelThingStuff');
227+
});
228+
});
229+
230+
describe('and that trimmed value is an empty string', () => {
231+
232+
beforeEach(() => {
233+
data.initialModel.treeNodeSpec.input.value = ' ';
234+
wrapper = createWrapper(data);
235+
});
236+
237+
it('should set the value to the label value, minus disallowed characters', () => {
238+
expect(wrapper.vm.model.treeNodeSpec.input.value).to.equal('ALabelThingStuff');
239+
});
240+
});
241+
});
242+
});
243+
244+
describe('when given an input model with no input state specified', () => {
245+
246+
beforeEach(() => {
247+
let data = getDefaultPropsData();
248+
data.initialModel = generateNodes(['c'])[0];
249+
data.initialModel.treeNodeSpec.state.input = null;
250+
wrapper = createWrapper(data);
251+
});
252+
253+
it('should default the disabled state to false', () => {
254+
expect(wrapper.vm.model.treeNodeSpec.state.input.disabled).to.be.false;
255+
});
256+
257+
describe('and the input is a checkbox', () => {
258+
259+
it('should set the value of the input to false', () => {
260+
expect(wrapper.vm.model.treeNodeSpec.state.input.value).to.be.false;
261+
});
137262
});
138263
});
139264

@@ -193,7 +318,7 @@ describe('TreeViewNode.vue', () => {
193318

194319
wrapper = createWrapper({
195320
ariaKeyMap: {},
196-
initialModel: generateNodes(['esa'], "", addChildCallback)[0],
321+
initialModel: generateNodes(['esa'], '', addChildCallback)[0],
197322
modelDefaults: {},
198323
depth: 0,
199324
treeId: 'tree',

0 commit comments

Comments
 (0)