Skip to content

Commit

Permalink
Merge pull request #153 from namecheap/cover/handle-page-transaction
Browse files Browse the repository at this point in the history
Cover handle page transaction with tests
  • Loading branch information
oleh-momot authored May 6, 2020
2 parents f2b3ffa + c432f99 commit a70ac8b
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 20 deletions.
12 changes: 6 additions & 6 deletions ilc/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as singleSpa from 'single-spa';
import Router from './common/router/ClientRouter';
import setupErrorHandlers from './client/errorHandler/setupErrorHandlers';
import {fragmentErrorHandlerFactory, crashIlc} from './client/errorHandler/fragmentErrorHandlerFactory';
import handlePageTransaction from './client/handlePageTransaction';
import handlePageTransaction, {slotWillBe} from './client/handlePageTransaction';
import initSpaConfig from './client/initSpaConfig';
import setupPerformanceMonitoring from './client/performance';
import selectSlotsToRegister from './client/selectSlotsToRegister';
Expand Down Expand Up @@ -75,9 +75,9 @@ function isActiveFactory(appName, slotName) {
let isActive = checkActivity(router.getCurrentRoute());
const wasActive = checkActivity(router.getPrevRoute());

let willBe;
!wasActive && isActive && (willBe = 'rendered');
wasActive && !isActive && (willBe = 'removed');
let willBe = slotWillBe.default;
!wasActive && isActive && (willBe = slotWillBe.rendered);
wasActive && !isActive && (willBe = slotWillBe.removed);

if (isActive && wasActive && reload === false) {
const oldProps = router.getPrevRouteProps(appName, slotName);
Expand All @@ -95,12 +95,12 @@ function isActiveFactory(appName, slotName) {
});

isActive = false;
willBe = 'rerendered';
willBe = slotWillBe.rerendered;
}
}

if (window.ilcConfig && window.ilcConfig.tmplSpinner) {
willBe && handlePageTransaction(slotName, willBe);
handlePageTransaction(slotName, willBe);
}

reload = false;
Expand Down
39 changes: 26 additions & 13 deletions ilc/client/handlePageTransaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,32 @@ const renderFakeSlot = slotName => {
hiddenSlots.push(targetNode);
};

/**
* @param {string} slotName
* @param {string} willBe - possible values: rendered, removed, rerendered
*/
export default function (slotName, willBe) {
if (!slotName || !willBe) return;
export const slotWillBe = {
rendered: 'rendered',
removed: 'removed',
rerendered: 'rerendered',
default: null,
};

export default function handlePageTransaction(slotName, willBe) {
if (!slotName) {
throw new Error('A slot name was not provided!');
}

if (willBe === 'rendered') {
addContentListener(slotName);
} else if (willBe === 'removed') {
renderFakeSlot(slotName);
} else if (willBe === 'rerendered') {
renderFakeSlot(slotName);
addContentListener(slotName);
switch (willBe) {
case slotWillBe.rendered:
addContentListener(slotName);
break;
case slotWillBe.removed:
renderFakeSlot(slotName);
break;
case slotWillBe.rerendered:
renderFakeSlot(slotName);
addContentListener(slotName);
break;
case slotWillBe.default:
break;
default:
throw new Error(`The slot action '${willBe}' did not match any possible values!`);
}
}
264 changes: 264 additions & 0 deletions ilc/client/handlePageTransaction.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import chai from 'chai';
import sinon from 'sinon';
import html from 'nanohtml';

import handlePageTransaction, {
slotWillBe,
} from './handlePageTransaction';

describe('handle page transaction', () => {
const locationHash = 'i-am-location-hash';

const slots = {
id: 'slots',
appendSlots: () => document.body.appendChild(slots.ref),
removeSlots: () => document.body.removeChild(slots.ref),
resetRef: () => {
slots.ref = html`
<div id="${slots.id}">
<div id="${slots.navbar.id}"></div>
<div id="${slots.body.id}"></div>
</div>
`;
},
navbar: {
id: 'navbar',
getComputedStyle: () => window.getComputedStyle(slots.navbar.ref, null),
},
body: {
id: 'body',
getComputedStyle: () => window.getComputedStyle(slots.body.ref, null),
getAttributeName: () => document.body.getAttribute('name'),
},
};

const applications = {
navbar: {
id: 'navbar-application',
class: 'navbar-spa',
appendApplication: () => slots.navbar.ref.appendChild(applications.navbar.ref),
resetRef: () => {
applications.navbar.ref = html`
<div id="${applications.navbar.id}" class="${applications.navbar.class}">
Hello! I am Navbar SPA
</div>
`;
},
},
body: {
id: 'body-application',
class: 'body-spa',
appendApplication: () => slots.body.ref.appendChild(applications.body.ref),
removeApplication: () => slots.body.ref.removeChild(applications.body.ref),
resetRef: () => {
applications.body.ref = html`
<div id="${applications.body.id}" class="${applications.body.class}">
Hello! I am Body SPA
</div>
`;
},
},
};

const spinner = {
id: 'ilc-spinner',
class: 'ilc-spinner',
getRef: () => document.getElementById(spinner.id),
};

let clock;

beforeEach(() => {
window.location.hash = locationHash;
window.ilcConfig = {
tmplSpinner: `<div id="${spinner.id}" class="${spinner.class}">Hello! I am Spinner</div>`,
};

slots.resetRef();
applications.body.resetRef();
applications.navbar.resetRef();

slots.appendSlots();

slots.navbar.ref = document.getElementById(slots.navbar.id);
slots.body.ref = document.getElementById(slots.body.id);

clock = sinon.useFakeTimers();
});

afterEach(() => {
slots.removeSlots();
clock.restore();
});

it('should throw an error when a slot name is not provided', async () => {
chai.expect(() => handlePageTransaction()).to.throw(
'A slot name was not provided!'
);
});

it('should throw an error when a slot action does not match any possible option to handle', async () => {
chai.expect(() => handlePageTransaction(slots.body.id, 'undefined')).to.throw(
`The slot action 'undefined' did not match any possible values!`
);
});

it('should do nothing when a slot action is default', async () => {
handlePageTransaction(slots.body.id, slotWillBe.default);

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.null;
chai.expect(slots.ref.innerHTML).to.be.equal(
`<div id="${slots.navbar.id}"></div>` +
`<div id="${slots.body.id}"></div>`
);

applications.body.appendApplication();

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.null;
chai.expect(slots.ref.innerHTML).to.be.equal(
`<div id="${slots.navbar.id}"></div>` +
`<div id="${slots.body.id}">` +
`<div id="${applications.body.id}" class="${applications.body.class}">` +
'Hello! I am Body SPA' +
'</div>' +
'</div>'
);

applications.body.removeApplication();

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.null;
chai.expect(slots.ref.innerHTML).to.be.equal(
`<div id="${slots.navbar.id}"></div>` +
`<div id="${slots.body.id}"></div>`
);
});

it('should listen to slot content changes when a slot is going to be rendered', async () => {
handlePageTransaction(slots.navbar.id, slotWillBe.rendered);

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.not.null;
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('none');
chai.expect(slots.body.getAttributeName()).to.be.equal(locationHash);

handlePageTransaction(slots.body.id, slotWillBe.rendered);

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.not.null;
chai.expect(document.getElementsByClassName(spinner.class).length).to.equal(1);
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('none');
chai.expect(slots.body.getComputedStyle().display).to.be.equal('none');
chai.expect(slots.body.getAttributeName()).to.be.equal(locationHash);

applications.navbar.appendApplication();

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.not.null;
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('none');
chai.expect(slots.body.getComputedStyle().display).to.be.equal('none');
chai.expect(slots.body.getAttributeName()).to.be.equal(locationHash);

applications.body.appendApplication();

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.null;
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('block');
chai.expect(slots.body.getComputedStyle().display).to.be.equal('block');
chai.expect(slots.body.getAttributeName()).to.be.null;
});

it('should render a fake slot when a slot is going to be removed', async () => {
applications.navbar.appendApplication();
applications.body.appendApplication();

handlePageTransaction(slots.body.id, slotWillBe.removed);

await clock.runAllAsync();

applications.body.removeApplication();

await clock.runAllAsync();

const bodyApplications = document.getElementsByClassName(applications.body.class);
chai.expect(bodyApplications.length).to.be.equal(1);

const [fakeBodyApplicationRef] = bodyApplications;
const fakeBodySlot = fakeBodyApplicationRef.parentNode;

chai.expect(fakeBodyApplicationRef.id).to.be.equal(applications.body.id);

chai.expect(window.getComputedStyle(fakeBodyApplicationRef, null).display).to.be.equal('block');
chai.expect(window.getComputedStyle(fakeBodySlot, null).display).to.be.equal('block');
chai.expect(slots.body.getComputedStyle().display).to.be.equal('none');
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('block');

chai.expect(fakeBodySlot.nodeName).to.be.equal(slots.body.ref.nodeName);
chai.expect(fakeBodySlot.id).to.be.equal('');
chai.expect(fakeBodySlot.className).to.be.equal('');

chai.expect(spinner.getRef()).to.be.null;
});

it('should render a fake slot and listen to slot content changes when a slot is going to be rerendered', async () => {
const newBodyApplication = {
id: 'new-body-application',
class: 'new-body-spa',
};

newBodyApplication.ref = html`
<div id="${newBodyApplication.id}" class="${newBodyApplication.class}">
Hello! I am new Body SPA
</div>
`;

applications.navbar.appendApplication();
applications.body.appendApplication();

handlePageTransaction(slots.body.id, slotWillBe.rerendered);

await clock.runAllAsync();

applications.body.removeApplication();

await clock.runAllAsync();

const bodyApplications = document.getElementsByClassName(applications.body.class);
chai.expect(bodyApplications.length).to.be.equal(1);

const [fakeBodyApplicationRef] = bodyApplications;
const fakeBodySlot = fakeBodyApplicationRef.parentNode;

chai.expect(fakeBodyApplicationRef.id).to.be.equal(applications.body.id);

chai.expect(window.getComputedStyle(fakeBodyApplicationRef, null).display).to.be.equal('block');
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('block');
chai.expect(slots.body.getComputedStyle().display).to.be.equal('none');
chai.expect(window.getComputedStyle(fakeBodySlot, null).display).to.be.equal('block');

chai.expect(fakeBodySlot.nodeName).to.be.equal(slots.body.ref.nodeName);
chai.expect(fakeBodySlot.id).to.be.equal('');
chai.expect(fakeBodySlot.className).to.be.equal('');

chai.expect(spinner.getRef()).to.be.not.null;
chai.expect(slots.body.getAttributeName()).to.be.equal(locationHash);

slots.body.ref.appendChild(newBodyApplication.ref);

await clock.runAllAsync();

chai.expect(spinner.getRef()).to.be.null;
chai.expect(slots.navbar.getComputedStyle().display).to.be.equal('block');
chai.expect(slots.body.getComputedStyle().display).to.be.equal('block');
chai.expect(slots.body.getAttributeName()).to.be.null;
});
});
3 changes: 2 additions & 1 deletion ilc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"test:watch": "npm run test -- --watch",
"test:coverage": "cross-env NODE_ENV=test nyc mocha",
"test:client": "npm run build:systemjs && cross-env NODE_ENV=test karma start",
"test:client:watch": "npm run test:client -- --no-single-run",
"test:client:watch": "npm run test:client -- --single-run=false --auto-watch=true",
"start": "node --max-http-header-size 30000 server/app.js",
"dev": "npm run build:polyfills && cross-env NODE_ENV=development nodemon --ignore './public/' --max-http-header-size 30000 server/app.js",
"build": "rimraf public && npm run build:systemjs && npm run build:client && npm run build:polyfills",
Expand Down Expand Up @@ -59,6 +59,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.2",
"mocha": "^7.1.1",
"nanohtml": "^1.9.1",
"nodemon": "^2.0.3",
"nyc": "^15.0.1",
"raw-loader": "^3.1.0",
Expand Down

0 comments on commit a70ac8b

Please sign in to comment.