
Lets Talk Tech!
Confession Time
Ok… Confession time! If you’re in the know, then all along you’ve been aware that this is a coding test for a job application! Yup. I know, right?
Ordinarily you wouldn’t find the following information on a product website, so I guess this section will make sense in that light. And it will also only make sense if you’re from a technical background.
Here’s how I put the project together…
Technology
Front-end
The widget is a static JavaScript library that’s built on top of Node but is completely independant of any server based technology at runtime. The only dependency it has is on an API call to a back-end server to submit the feedback detail.
The entire widget code is packaged into a single .js file using Webpack and it can then be loaded into a browser via code, inclusion in a <script> tag or via an AMD or SystemJS loader.
In terms of the front-end technology stack, the widget uses:
- Modules (specified as a library in Webpack) to keep the widget fully encapsulated and abstracted;
- Polymer and Webcomponents to provide the UI elements within Shaddow DOM to further isolate the widget and prevent leaky CSS. This protects both the widget code and the code on the host page from interference from each other;
- Redux for application state management. Thunk Redux as middleware for side-effects and async requirements.
Back-end
The back-end API is an Azure Serverless Function App connecting into an Azure Cosmos DB instance in the cloud.
Loading the Widget
The widget is flexible in the way it can be loaded. As a UMD library it can be loaded via an AMD or SystemJS loader, but it can also be loaded directly by the browser via a <script> tag or JavaScript.
The widget .js file won’t run completely by itself though. As a fairly new technology, Polymer on top of Webcomponents is only natively supported by Chrome. Other browsers need to load the appropriate polyfills in order for the widget to run. We also need to load a Promise polyfill for older browsers as this feature is used in the widget.
The next section explains the soure that loads the widget that was detailed in the Get Started section.
The following loads a Promise polyfill for backwards compatabilty:
1
<script src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
This is a generic function, utilising a Promise, that allows script files to be loaded via JavaScript:
1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
function injectScript(src) {
return new Promise(function (resolve, reject) {
const script = document.createElement('script');
script.async = true;
script.src = src;
script.addEventListener('load', resolve);
script.addEventListener('error', function () { reject('Error loading script.') });
script.addEventListener('abort', function () { reject('Script loading aborted.') });
document.head.appendChild(script);
});
}
This function is called by the host page to initialise the widget. It loads:
- “custom-elements-es5-adapter.js” - needed to adapt browsers that already support webcomponents;
- “webcomponents-loader.js” - this script dynamically loads the webcomponents polyfills depending on the level of support needed by the particular browser;
- Via an event listener which advises when the Webcomponents polyfills have been loaded, “codered_nps_widget.js” - this is the widget code;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function initialzeCodeRedNpsWidget(config) {
injectScript('https://code-red-solutions.github.io/NPS-CodeTest/lib/dev/custom-elements-es5-adapter.js')
.then(function () {
console.log('Script \'custom-elements-es5-adapter.js\' loaded.');
injectScript('https://code-red-solutions.github.io/NPS-CodeTest/lib/dev/webcomponents-loader.js')
.then(function () {
console.log('Script \'webcomponents-loader.js\' loaded.');
document.addEventListener('WebComponentsReady',
function (e) {
console.log('Event WebComponentsReady fired.');
injectScript('https://code-red-solutions.github.io/NPS-CodeTest/lib/dev/codered_nps_widget.js')
.then(function () {
console.log('Script \'codered_nps_widget.js\' loaded.');
const npsWidget = new (codered_nps_widget.NpsWidget)(config);
document.getElementsByTagName('body')[0].appendChild(npsWidget);
npsWidget.render();
}).catch(function (error) {
console.log(error);
});
});
}).catch(function (error) {
console.log(error);
});
}).catch(function (error) {
console.log(error);
});
}
</script>
You’ll note that after the widget code has been loaded, the above also creates a new instance of the widget, attaches it to the DOM and calls the widget’s render method.
The widget need not be loaded this way. As long as the following occur, the widget will execute correctly:
- Load any
Promisepolyfill - Load “custom-elements-es5-adapter.js”
- Load “webcomponents-loader.js”
- On the “WebComponentsReady” event, load “codered_nps_widget.js”
- Create a new instance of the widget and call it’s constructor, with or without a config
- Call the widget’s render method
Unit Testing
Front-end
Unit testing in the front-end runs on Node within the develoment server. It uses ts-node as a compilation engine, Mocha as a test runner, Chai for assertions extensions and TypeMoq as a JavaScript mocking library.
Back-end
Unit testing in the back-end runs within Visual Studio on the development server. It can use any standard test runner that works within Visual Studio, such as Visual Studio itself, or a relevant pluggin such as the ReSharper test runner. It uses the ‘Should’ library for assertion extensions and Moq as a mocking libary.
More Information
There’s a debrief section in this website called ‘Development Experience’. For more information on the current state of the unit tests, you can lookup the details in Development Experience > Unit Testing.