Scaffolding, base structure, base backend
This commit is contained in:
commit
a9eaa5dbde
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Prueba CGI</title>
|
||||||
|
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module" src="src/main.js" defer></script>
|
||||||
|
<main id="root"></main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import Store from '../modules/store.js';
|
||||||
|
import text from '../modules/text.js';
|
||||||
|
|
||||||
|
class AppBase extends HTMLElement {
|
||||||
|
static observedAttributes = ['title', 'description'];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
this.text = text;
|
||||||
|
this.store = new Store();
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.store.subscribe(this.render.bind(this));
|
||||||
|
this.render();
|
||||||
|
|
||||||
|
console.log('Component constructed', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
console.log('Component disconnected', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderText() {
|
||||||
|
const textList = this.store.getRecords();
|
||||||
|
const selected = this.store.getSelected();
|
||||||
|
return textList.map((item) => `<option value=${item.id} ${selected.includes(item.id) && "selected"}>${item.text}</option>`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<main-window title="${this.text.title}" description="${this.text.description}">
|
||||||
|
<select multiple is="item-list">
|
||||||
|
${this.renderText()}
|
||||||
|
</select>
|
||||||
|
<span id="button-bar">
|
||||||
|
<custom-button action="${Store.actions.REMOVE_LINE}" color="#324bff">Delete</custom-button>
|
||||||
|
<custom-button action="${Store.actions.ADD_LINE}" color="#324bff">Add</custom-button>
|
||||||
|
<custom-button action="${Store.actions.UNDO}" outline color="#324bff">Undo</custom-button>
|
||||||
|
</span>
|
||||||
|
</main-window>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('app-base', AppBase);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import Store from '../modules/store.js';
|
||||||
|
|
||||||
|
class CustomButton extends HTMLElement {
|
||||||
|
static observedAttributes = ['outline', 'color', 'action'];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.publish = Store.publish;
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
}
|
||||||
|
|
||||||
|
onClick(e) {
|
||||||
|
this.publish(this.getAttribute('action'));
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.render();
|
||||||
|
this.shadowRoot.getElementById('button').addEventListener('click', this.onClick.bind(this));
|
||||||
|
|
||||||
|
console.log('Component constructed', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.shadowRoot.getElementById('button').removeEventListener('click', this.onClick);
|
||||||
|
|
||||||
|
console.log('Component disconnected', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const isOutline = this.getAttribute('outline') !== null && this.getAttribute('outline') !== 'false';
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
border-radius: 50px;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: ${isOutline ? 'solid' : 'hidden'};
|
||||||
|
border-color: ${this.getAttribute('color')};
|
||||||
|
opacity: 1;
|
||||||
|
width: 138px;
|
||||||
|
height: 49px;
|
||||||
|
background-color: ${isOutline ? 'white' : this.getAttribute('color')};
|
||||||
|
color: ${isOutline ? this.getAttribute('color') : 'white'};
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Montserrat", sans-serif;
|
||||||
|
font-weight: normal
|
||||||
|
letter-spacing: 0px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<button id="button"><slot></slot></button>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('custom-button', CustomButton);
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import Store from '../modules/store.js';
|
||||||
|
|
||||||
|
class ItemList extends HTMLSelectElement {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.publish = Store.publish
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.addEventListener('click', this.onChange.bind(this));
|
||||||
|
this.updateStyle();
|
||||||
|
|
||||||
|
console.log('Component constructed', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange() {
|
||||||
|
const selectedIds = Array.from(this.selectedOptions).map((option) => option.value);
|
||||||
|
this.publish(Store.actions.SELECT, selectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
this.removeEventListener('change', this.onChange.bind(this));
|
||||||
|
|
||||||
|
console.log('Component disconnected', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
this.updateStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStyle() {
|
||||||
|
this.style.width = '800px';
|
||||||
|
this.style.height = '227px';
|
||||||
|
this.style.background = '#F7F7F7 0% 0% no-repeat padding-box';
|
||||||
|
this.style.border = '1px solid #CCCCCC';
|
||||||
|
this.style.opacity = '1';
|
||||||
|
this.style.fontSize = '18px';
|
||||||
|
this.style.fontFamily = '"Montserrat" sans-serif';
|
||||||
|
this.style.fontWeight = 'normal';
|
||||||
|
this.style.letterSpacing = '0px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('item-list', ItemList, { extends: 'select' });
|
||||||
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
class MainWindow extends HTMLElement {
|
||||||
|
static observedAttributes = ['title', 'description'];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.attachShadow({ mode: 'open' });
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
this.render();
|
||||||
|
console.log('Component constructed', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
console.log('Component disconnected', this);
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangedCallback() {
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const title = this.getAttribute('title') || "No title";
|
||||||
|
const description = this.getAttribute('description') || "No description";
|
||||||
|
|
||||||
|
this.shadowRoot.innerHTML = `
|
||||||
|
<style>
|
||||||
|
div {
|
||||||
|
width: 900px;
|
||||||
|
height: 577px;
|
||||||
|
background: #FFFFFF 0% 0% no-repeat padding-box;
|
||||||
|
box-shadow: 0px 5px 12px #0000001F;
|
||||||
|
border-radius: 20px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
width: 800px;
|
||||||
|
margin: 0 50px 13px 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
top: 221px;
|
||||||
|
margin: 50px 50px 13px 50px;
|
||||||
|
height: 49px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 40px;
|
||||||
|
font-family: "Montserrat", sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
color: #333333;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0 50px 0 50px;
|
||||||
|
text-align: center;
|
||||||
|
height: 74px;
|
||||||
|
|
||||||
|
/* UI Properties */
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
font-family: "Montserrat", sans-serif;
|
||||||
|
font-weight: normal;
|
||||||
|
letter-spacing: 0px;
|
||||||
|
color: #333333;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
slot {
|
||||||
|
margin: 0 50px 0 50px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div>
|
||||||
|
<h1>${title}</h1>
|
||||||
|
<p>${description}</p>
|
||||||
|
<span><slot></slot></span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('main-window', MainWindow);
|
||||||
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import './components/app-base.js';
|
||||||
|
import './components/main-window.js';
|
||||||
|
import './components/custom-button.js';
|
||||||
|
import './components/item-list.js';
|
||||||
|
|
||||||
|
class App {
|
||||||
|
constructor(root, text, store) {
|
||||||
|
this.root = root;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.root.innerHTML = `
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background: transparent linear-gradient(135deg, #A1C4FD 0%, #C2E9FB 100%) 0% 0% no-repeat padding-box;
|
||||||
|
opacity: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<app-base></app-base>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new App(document.getElementById('root'));
|
||||||
|
|
||||||
|
app.start();
|
||||||
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
export default class Store {
|
||||||
|
static actions = {
|
||||||
|
ADD_LINE: 'ADD_LINE',
|
||||||
|
REMOVE_LINE: 'REMOVE_LINE',
|
||||||
|
UNDO: 'UNDO',
|
||||||
|
SELECT: 'SELECT',
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.state = {
|
||||||
|
records: [],
|
||||||
|
selected: [],
|
||||||
|
};
|
||||||
|
this.callbacks = [];
|
||||||
|
|
||||||
|
Object.values(this.constructor.actions).forEach(action => document.addEventListener(action, this.eventHandler.bind(this), false));
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(callback) {
|
||||||
|
this.callbacks = [ ...this.callbacks, callback ];
|
||||||
|
}
|
||||||
|
|
||||||
|
static publish(action, data = {}) {
|
||||||
|
if (!Object.values(Store.actions).includes(action)) {
|
||||||
|
console.error(`Action ${action} is not valid`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = new CustomEvent(action, { detail: data });
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
eventHandler(event) {
|
||||||
|
if (!Object.values(this.constructor.actions).includes(event.type)) {
|
||||||
|
console.error(`Action "${event.type}" is not valid`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case this.constructor.actions.ADD_LINE:
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
records: this.state.records.concat({
|
||||||
|
id: this.state.records.length,
|
||||||
|
text: 'asdf',
|
||||||
|
action: 'add',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case this.constructor.actions.REMOVE_LINE:
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
records: this.records.concat({
|
||||||
|
id: this.state.records.length,
|
||||||
|
action: 'remove',
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case this.constructor.actions.UNDO:
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
records: this.state.records.slice(0, -1),
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case this.constructor.actions.SELECT:
|
||||||
|
this.state = {
|
||||||
|
...this.state,
|
||||||
|
selected: event.detail,
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error(`Action "${event.type}" not implemented in eventHandler`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.callbacks.forEach((cb) => cb());
|
||||||
|
}
|
||||||
|
|
||||||
|
getRecords() {
|
||||||
|
return this.state.records.reduce((acc, current) => {
|
||||||
|
if (current.action === "add") {
|
||||||
|
return acc.concat({id: current.id, text: current.text});
|
||||||
|
}
|
||||||
|
if (current.action === "remove") {
|
||||||
|
return acc.filter((item) => item.id !== current.targetId);
|
||||||
|
}
|
||||||
|
console.error(`Action ${current.action} not valid`);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelected() {
|
||||||
|
return this.state.selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
title: 'This is a technical proof',
|
||||||
|
description: 'Lorem ipsum dolor sit amet consectetur adipiscing, elit mus primis nec inceptos. Lacinia habitasse arcu molestie maecenas cursus quam nunc, hendrerit posuere augue fames dictumst placerat porttitor, dis mi pharetra vestibulum venenatis phasellus.',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Test suite</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module" src="./main.js" defer></script>
|
||||||
|
<pre id=root>
|
||||||
|
Test suite
|
||||||
|
|
||||||
|
</pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import StoreTest from './modules/store.spec.js';
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
constructor(root) {
|
||||||
|
this.root = root;
|
||||||
|
this.tests = [];
|
||||||
|
this.pass = 0
|
||||||
|
this.fail = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.defineTests();
|
||||||
|
this.tests.forEach((testClass) => {
|
||||||
|
this.log(`Testing ${testClass.constructor.name}`);
|
||||||
|
testClass.test();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this.log(`
|
||||||
|
|
||||||
|
Passed ${this.pass} tests
|
||||||
|
Failed ${this.fail} tests
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineTests() {
|
||||||
|
this.tests = [
|
||||||
|
new StoreTest(this.assert.bind(this)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(description, current, expected) {
|
||||||
|
const sameType = typeof current === typeof expected;
|
||||||
|
const sameValue = JSON.stringify(current) === JSON.stringify(expected);
|
||||||
|
let result = '[ERROR]';
|
||||||
|
if (sameType && sameValue) {
|
||||||
|
this.log(` [PASS] ${description}`);
|
||||||
|
this.pass++;
|
||||||
|
} else {
|
||||||
|
this.log(` [FAIL] ${description}. Expected (${typeof expected}) ${JSON.stringify(expected)}, got (${typeof current}) ${JSON.stringify(current)}`);
|
||||||
|
this.fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log(text) {
|
||||||
|
this.root.insertAdjacentText("beforeend", `${text}\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testSuite = new Test(document.getElementById('root'));
|
||||||
|
testSuite.start();
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
import Store from '../../src/modules/store.js';
|
||||||
|
|
||||||
|
export default class StoreTest {
|
||||||
|
constructor(assert) {
|
||||||
|
this.assert = assert;
|
||||||
|
}
|
||||||
|
|
||||||
|
test() {
|
||||||
|
(() => {
|
||||||
|
let called = false
|
||||||
|
const store = new Store(() => called = true);
|
||||||
|
|
||||||
|
const event = new Event('addLine');
|
||||||
|
store.eventHandler(event);
|
||||||
|
|
||||||
|
this.assert("eventHandler calls the callback after successfull event", called, true);
|
||||||
|
|
||||||
|
})();
|
||||||
|
(() => {
|
||||||
|
const store = new Store(() => {});
|
||||||
|
store.records = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
{id: 1, text: "qwer", action: 'add'},
|
||||||
|
{id: 2, action: 'remove'},
|
||||||
|
]
|
||||||
|
|
||||||
|
const event = new Event('unknown');
|
||||||
|
store.eventHandler(event);
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
{id: 1, text: "qwer", action: 'add'},
|
||||||
|
{id: 2, action: 'remove'},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.assert("eventHandler does nothing on an unknown event type", store.records, expected);
|
||||||
|
|
||||||
|
})();
|
||||||
|
(() => {
|
||||||
|
const store = new Store(() => {});
|
||||||
|
|
||||||
|
const event = new Event('addLine');
|
||||||
|
store.eventHandler(event);
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.assert("eventHandler adds the appropiate record when Event 'addLine' is passed", store.records, expected);
|
||||||
|
|
||||||
|
})();
|
||||||
|
(() => {
|
||||||
|
const store = new Store(() => {});
|
||||||
|
store.records = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
]
|
||||||
|
|
||||||
|
const event = new Event('removeLine');
|
||||||
|
store.eventHandler(event);
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
{id: 1, action: 'remove'},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.assert("eventHandler adds the appropiate record when Event 'removeLine' is passed", store.records, expected);
|
||||||
|
|
||||||
|
})();
|
||||||
|
(() => {
|
||||||
|
const store = new Store(() => {});
|
||||||
|
store.records = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
{id: 1, text: "qwer", action: 'add'},
|
||||||
|
{id: 2, action: 'remove'},
|
||||||
|
]
|
||||||
|
|
||||||
|
const event = new Event('undo');
|
||||||
|
store.eventHandler(event);
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{id: 0, text: "asdf", action: 'add'},
|
||||||
|
{id: 1, text: "qwer", action: 'add'},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.assert("eventHandler deletes the appropiate record when Event 'undo' is passed", store.records, expected);
|
||||||
|
|
||||||
|
})();
|
||||||
|
(() => {
|
||||||
|
const store = new Store(() => {});
|
||||||
|
|
||||||
|
store.records = [
|
||||||
|
{id: 0, text: "Line 1", action: 'add'},
|
||||||
|
{id: 1, text: "Line 2", action: 'add'},
|
||||||
|
{id: 2, text: "Line 3", action: 'add'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{id: 0, text: "Line 1"},
|
||||||
|
{id: 1, text: "Line 2"},
|
||||||
|
{id: 2, text: "Line 3"},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.assert("getState returns an array of strings with 3 add records", store.getState(), expected);
|
||||||
|
})();
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const store = new Store(() => {});
|
||||||
|
|
||||||
|
store.records = [
|
||||||
|
{id: 0, text: "Line 1", action: 'add'},
|
||||||
|
{id: 1, text: "Line 2", action: 'add'},
|
||||||
|
{id: 2, text: "Line 3", action: 'add'},
|
||||||
|
{id: 3, targetId: 1, action: 'remove'},
|
||||||
|
];
|
||||||
|
|
||||||
|
const expected = [
|
||||||
|
{id: 0, text: "Line 1"},
|
||||||
|
{id: 2, text: "Line 3"},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.assert("getState returns an array of strings with 3 add records and a delete record", store.getState(), expected);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue