changed fetchMapUi epic to add extra param - initialNode added util method to open path in the map by node name changed action handler for fetchMapUi.complete to open initialNode changed map component to set scroll on component mount and update added attribute to challenge component to find challenge node by name extracted createEventMetaCreator into separate file to break circular dependencies Closes #16248
320 lines
7.9 KiB
JavaScript
320 lines
7.9 KiB
JavaScript
import test from 'tape';
|
|
import sinon from 'sinon';
|
|
|
|
import {
|
|
getNode,
|
|
createMapUi,
|
|
traverseMapUi,
|
|
updateSingleNode,
|
|
toggleThisPanel,
|
|
expandAllPanels,
|
|
collapseAllPanels,
|
|
updatePath,
|
|
openPath
|
|
} from './utils.js';
|
|
|
|
test('createMapUi', t => {
|
|
t.plan(3);
|
|
t.test('should return an `{}` when proper args not supplied', t => {
|
|
t.plan(3);
|
|
t.equal(
|
|
Object.keys(createMapUi()).length,
|
|
0
|
|
);
|
|
t.equal(
|
|
Object.keys(createMapUi({}, [])).length,
|
|
0
|
|
);
|
|
t.equal(
|
|
Object.keys(createMapUi({ superBlock: {} }, [])).length,
|
|
0
|
|
);
|
|
});
|
|
t.test('should return a map tree', t => {
|
|
const expected = {
|
|
children: [{
|
|
name: 'superBlockA',
|
|
children: [{
|
|
name: 'blockA',
|
|
children: [{
|
|
name: 'challengeA'
|
|
}]
|
|
}]
|
|
}]
|
|
};
|
|
const actual = createMapUi({
|
|
superBlock: {
|
|
superBlockA: {
|
|
blocks: [
|
|
'blockA'
|
|
]
|
|
}
|
|
},
|
|
block: {
|
|
blockA: {
|
|
challenges: [
|
|
'challengeA'
|
|
]
|
|
}
|
|
}
|
|
},
|
|
{ superBlocks: ['superBlockA'] },
|
|
{ challengeA: 'ChallengeA title'}
|
|
);
|
|
t.plan(3);
|
|
t.equal(actual.children[0].name, expected.children[0].name);
|
|
t.equal(
|
|
actual.children[0].children[0].name,
|
|
expected.children[0].children[0].name
|
|
);
|
|
t.equal(
|
|
actual.children[0].children[0].children[0].name,
|
|
expected.children[0].children[0].children[0].name
|
|
);
|
|
});
|
|
t.test('should protect against malformed data', t => {
|
|
t.plan(2);
|
|
t.equal(
|
|
createMapUi({
|
|
superBlock: {},
|
|
block: {
|
|
blockA: {
|
|
challenges: [
|
|
'challengeA'
|
|
]
|
|
}
|
|
}
|
|
}, { superBlocks: ['superBlockA'] }).children[0].children.length,
|
|
0
|
|
);
|
|
t.equal(
|
|
createMapUi({
|
|
superBlock: {
|
|
superBlockA: {
|
|
blocks: [
|
|
'blockA'
|
|
]
|
|
}
|
|
},
|
|
block: {}
|
|
},
|
|
{ superBlocks: ['superBlockA'] }).children[0].children[0].children.length,
|
|
0
|
|
);
|
|
});
|
|
});
|
|
test('traverseMapUi', t => {
|
|
t.test('should return tree', t => {
|
|
t.plan(2);
|
|
const expectedTree = {};
|
|
const actaulTree = traverseMapUi(expectedTree, tree => {
|
|
t.equal(tree, expectedTree);
|
|
return tree;
|
|
});
|
|
t.equal(actaulTree, expectedTree);
|
|
});
|
|
t.test('should hit every node', t => {
|
|
t.plan(4);
|
|
const expected = { children: [{ children: [{}] }] };
|
|
const spy = sinon.spy(t => t);
|
|
spy.withArgs(expected);
|
|
spy.withArgs(expected.children[0]);
|
|
spy.withArgs(expected.children[0].children[0]);
|
|
traverseMapUi(expected, spy);
|
|
t.equal(spy.callCount, 3);
|
|
t.ok(spy.withArgs(expected).calledOnce, 'foo');
|
|
t.ok(spy.withArgs(expected.children[0]).calledOnce, 'bar');
|
|
t.ok(spy.withArgs(expected.children[0].children[0]).calledOnce, 'baz');
|
|
});
|
|
t.test('should create new object when children change', t => {
|
|
t.plan(9);
|
|
const expected = { children: [{ bar: true }, {}] };
|
|
const actual = traverseMapUi(expected, node => ({ ...node, foo: true }));
|
|
t.notEqual(actual, expected);
|
|
t.notEqual(actual.children, expected.children);
|
|
t.notEqual(actual.children[0], expected.children[0]);
|
|
t.notEqual(actual.children[1], expected.children[1]);
|
|
t.equal(actual.children[0].bar, expected.children[0].bar);
|
|
t.notOk(expected.children[0].foo);
|
|
t.notOk(expected.children[1].foo);
|
|
t.true(actual.children[0].foo);
|
|
t.true(actual.children[1].foo);
|
|
});
|
|
});
|
|
test('getNode', t => {
|
|
t.test('should return node', t => {
|
|
t.plan(1);
|
|
const expected = { name: 'foo' };
|
|
const tree = { children: [{ name: 'notfoo' }, expected ] };
|
|
const actual = getNode(tree, 'foo');
|
|
t.equal(expected, actual);
|
|
});
|
|
t.test('should returned undefined if not found', t => {
|
|
t.plan(1);
|
|
const tree = {
|
|
children: [ { name: 'foo' }, { children: [ { name: 'bar' } ] } ]
|
|
};
|
|
const actual = getNode(tree, 'baz');
|
|
t.notOk(actual);
|
|
});
|
|
});
|
|
test('updateSingleNode', t => {
|
|
t.test('should update single node', t => {
|
|
const expected = { name: 'foo' };
|
|
const untouched = { name: 'notFoo' };
|
|
const actual = updateSingleNode(
|
|
{ children: [ untouched, expected ] },
|
|
'foo',
|
|
node => ({ ...node, tag: true })
|
|
);
|
|
t.plan(4);
|
|
t.ok(actual.children[1].tag);
|
|
t.equal(actual.children[1].name, expected.name);
|
|
t.notEqual(actual.children[1], expected);
|
|
t.equal(actual.children[0], untouched);
|
|
});
|
|
});
|
|
test('toggleThisPanel', t => {
|
|
t.test('should update single node', t => {
|
|
const expected = { name: 'foo', isOpen: true };
|
|
const actual = toggleThisPanel(
|
|
{ children: [ { name: 'foo', isOpen: false }] },
|
|
'foo'
|
|
);
|
|
t.plan(1);
|
|
t.deepLooseEqual(actual.children[0], expected);
|
|
});
|
|
});
|
|
test('toggleAllPanels', t => {
|
|
t.test('should add `isOpen: true` to every node without children', t => {
|
|
const expected = {
|
|
isOpen: true,
|
|
children: [{
|
|
isOpen: true,
|
|
children: [{}, {}]
|
|
}]
|
|
};
|
|
const actual = expandAllPanels({ children: [{ children: [{}, {}] }] });
|
|
t.plan(1);
|
|
t.deepLooseEqual(actual, expected);
|
|
});
|
|
t.test('should add `isOpen: false` to every node without children', t => {
|
|
const leaf = {};
|
|
const expected = {
|
|
isOpen: false,
|
|
children: [{
|
|
isOpen: false,
|
|
children: [{}, leaf]
|
|
}]
|
|
};
|
|
const actual = collapseAllPanels(
|
|
{ isOpen: true, children: [{ children: [{}, leaf]}]},
|
|
);
|
|
t.plan(2);
|
|
t.deepLooseEqual(actual, expected);
|
|
t.equal(actual.children[0].children[1], leaf);
|
|
});
|
|
});
|
|
test('updatePath', t => {
|
|
t.test('should call update function for each node in the path', t => {
|
|
const expected = {
|
|
children: [
|
|
{
|
|
name: 'superFoo',
|
|
children: [
|
|
{
|
|
name: 'blockBar',
|
|
children: [{name: 'challBar'}]
|
|
},
|
|
{
|
|
name: 'blockFoo',
|
|
children: [{name: 'challFoo'}]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: 'superBaz',
|
|
isOpen: false,
|
|
children: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const spy = sinon.spy(t => ({ ...t}) );
|
|
spy.withArgs(expected.children[0]);
|
|
spy.withArgs(expected.children[0].children[1]);
|
|
spy.withArgs(expected.children[0].children[1].children[0]);
|
|
updatePath(expected, 'challFoo', spy);
|
|
t.plan(4);
|
|
t.equal(spy.callCount, 3);
|
|
t.ok(spy.withArgs(expected.children[0]).calledOnce, 'superBlock');
|
|
t.ok(spy.withArgs(expected.children[0].children[1]).calledOnce, 'block');
|
|
t.ok(
|
|
spy.withArgs(expected.children[0].children[1].children[0]).calledOnce,
|
|
'chall'
|
|
);
|
|
});
|
|
});
|
|
test('openPath', t=> {
|
|
t.test('should open all nodes in the path', t => {
|
|
const expected = {
|
|
children: [
|
|
{
|
|
name: 'superFoo',
|
|
isOpen: true,
|
|
children: [
|
|
{
|
|
name: 'blockBar',
|
|
isOpen: false,
|
|
children: []
|
|
},
|
|
{
|
|
name: 'blockFoo',
|
|
isOpen: true,
|
|
children: [{
|
|
name: 'challFoo'
|
|
}]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: 'superBar',
|
|
isOpen: false,
|
|
children: []
|
|
}
|
|
]
|
|
};
|
|
|
|
const actual = openPath({
|
|
children: [
|
|
{
|
|
name: 'superFoo',
|
|
isOpen: false,
|
|
children: [
|
|
{
|
|
name: 'blockBar',
|
|
isOpen: false,
|
|
children: []
|
|
},
|
|
{
|
|
name: 'blockFoo',
|
|
isOpen: false,
|
|
children: [{
|
|
name: 'challFoo'
|
|
}]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
name: 'superBar',
|
|
isOpen: false,
|
|
children: []
|
|
}
|
|
]
|
|
}, 'challFoo');
|
|
|
|
t.plan(1);
|
|
t.deepLooseEqual(actual, expected);
|
|
});
|
|
});
|