diff --git a/tests/javascripts/fullscreenTable.test.js b/tests/javascripts/fullscreenTable.test.js
new file mode 100644
index 000000000..0db261211
--- /dev/null
+++ b/tests/javascripts/fullscreenTable.test.js
@@ -0,0 +1,370 @@
+const helpers = require('./support/helpers');
+
+beforeAll(() => {
+ // set up jQuery
+ window.jQuery = require('jquery');
+ $ = window.jQuery;
+
+ // load module code
+ require('govuk_frontend_toolkit/javascripts/govuk/modules.js');
+ require('../../app/assets/javascripts/fullscreenTable.js');
+});
+
+afterAll(() => {
+ window.jQuery = null;
+ $ = null;
+
+ delete window.GOVUK;
+});
+
+describe('FullscreenTable', () => {
+ let container;
+ let tableFrame;
+ let table;
+ let numberColumnFrame;
+
+ function setWindowHeightTo (height) {
+
+ // mock DOM calls for window height
+ window.innerHeight = height;
+ // remove calls to document.documentElement.clientHeight when jQuery is gone. It's called to support older browsers like IE8
+ jest.spyOn(document.documentElement, 'clientHeight', 'get').mockImplementation(() => height);
+
+ };
+
+ function setWindowWidthTo (width) {
+
+ // mock DOM calls for window width
+ window.innerWidth = width;
+ // remove calls to document.documentElement.clientWidth when jQuery is gone. It's called to support older browsers like IE8
+ jest.spyOn(document.documentElement, 'clientWidth', 'get').mockImplementation(() => height);
+
+ };
+
+ function resizeWindowTo (dimensions) {
+
+ setWindowHeightTo(dimensions.height);
+ setWindowWidthTo(dimensions.width);
+ helpers.triggerEvent(window, 'resize');
+
+ };
+
+ function scrollWindowBy (scrollTop) {
+
+ document.documentElement.scrollTop = scrollTop;
+ helpers.triggerEvent(window, 'scroll');
+
+ };
+
+ function resetWindowDimensions () {
+
+ setWindowHeightTo(768);
+ setWindowWidthTo(1024);
+
+ };
+
+ function setElementDimensionsAndOffset (el, opts) {
+
+ const elOffsetSpy = jest.spyOn(el, 'getBoundingClientRect');
+
+ if (opts.height) {
+ el.setAttribute('style', `height: ${opts.height}px`);
+ }
+
+ if (opts.width) {
+ el.setAttribute('style', `width: ${opts.width}px`);
+ }
+
+ if (opts.offset) {
+ elOffsetSpy.mockImplementation(() => opts.offset);
+ }
+
+ };
+
+ function setElementWidth (el, width) {
+
+ el.setAttribute('style', `.width: ${width}px`);
+
+ };
+
+ beforeEach(() => {
+
+ const tableHeadings = () => {
+ let result = '';
+ const headings = ['1', 'name', 'email address', 'age', 'building number', 'address line 1', 'address line 2', 'postcode'];
+
+ headings.forEach((heading, idx) => {
+ if (idx === 0) {
+ result += `
+ Row in file${heading}
+ | `;
+ } else {
+ result += `
+ ${heading}
+ | `;
+ }
+ });
+
+ return result;
+ }
+
+ const rowCells = (cells) => {
+ let result = '';
+
+ Object.keys(cells).forEach((key, idx) => {
+ if (idx === 0) {
+ result += `
+
+ ${key}
+
+ | `;
+ } else {
+ result += `
+
+ ${key}
+
+ | `;
+ }
+ });
+
+ return result;
+ };
+
+ const tableRows = () => {
+ let result = '';
+
+ const rows = [
+ ['John Logie Baird', 'johnlbaird@gmail.com', '37', '22', 'Frith Street', 'Soho, London', 'W1D 4RF'],
+ ['Guglielmo Marconi', 'gmarconi@hotmail.com', '21', 'Pontecchio Marconi', 'Via Celestini 1', 'Bologna', ''],
+ ['Louis Braille', 'louisbraille@yahoo.co.uk', '', '56', 'Boulevard des Invalides', 'Paris', '75007'],
+ ['Ray Tomlinson', 'hedy.lamarr@msn.com', '25', '870', '870 Winter Street', 'Waltham', 'MA 02451']
+ ];
+
+ rows.forEach(row => {
+ result += `${rowCells(row)}
`;
+ });
+
+ return result;
+
+ }
+
+ // set up DOM
+ document.body.innerHTML =
+ `
+
+
+
+ people.csv
+
+
+
+ ${tableHeadings()}
+
+
+
+ ${tableRows()}
+
+
+
+ `;
+
+ container = document.querySelector('.fullscreen-content');
+
+ });
+
+ afterEach(() => {
+
+ document.body.innerHTML = '';
+
+ });
+
+ describe("when it loads", () => {
+
+ beforeEach(() => {
+
+ // start module
+ window.GOVUK.modules.start();
+
+ });
+
+ test("it fixes the number column for each row without changing the semantics", () => {
+
+ tableFrame = document.querySelector('.fullscreen-scrollable-table');
+ numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
+
+ expect(tableFrame).not.toBeNull();
+ expect(numberColumnFrame).not.toBeNull();
+ expect(numberColumnFrame.getAttribute('role')).toEqual('presentation');
+
+ });
+
+ });
+
+ describe("the height of the table should fit the vertical space available to it", () => {
+
+ beforeEach(() => {
+
+ // set the height and offset of the window and table container from the top of the document
+ // so just the top 268px of it appears on-screen
+ setWindowHeightTo(768);
+ setElementDimensionsAndOffset(container, { height: 1000, offset: { top: 500 } });
+
+ // start module
+ window.GOVUK.modules.start();
+
+ tableFrame = document.querySelector('.fullscreen-scrollable-table');
+ numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
+
+ });
+
+ afterEach(() => {
+
+ jest.clearAllMocks();
+ resetWindowDimensions();
+ scrollWindowBy(0);
+
+ });
+
+ test("when the page has loaded", () => {
+
+ // the frames should crop to the top 268px of the table that is visible
+ expect(window.getComputedStyle(tableFrame)['height']).toEqual('268px');
+ expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('268px');
+
+ });
+
+ test("when the page has scrolled", () => {
+
+ // scroll the window so the table fills the height of the window (768px)
+ scrollWindowBy(500);
+
+ // the frames should crop to the window height
+ expect(window.getComputedStyle(tableFrame)['height']).toEqual('768px');
+ expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('768px');
+
+ });
+
+ test("when the page has resized", () => {
+
+ // resize the window by 232px (from 768px to 1000px)
+ resizeWindowTo({ height: 1000, width: 1024 });
+
+ // the frames should crop to the top 500px of the table now visible
+ expect(window.getComputedStyle(tableFrame)['height']).toEqual('500px');
+ expect(window.getComputedStyle(numberColumnFrame)['height']).toEqual('500px');
+
+ });
+
+ });
+
+ describe("the width of the table should fit the horizontal space available to it", () => {
+ let rowNumberColumnCell;
+
+ beforeEach(() => {
+
+ rowNumberColumnCell = container.querySelector('.table-field-index');
+
+ // set main content column width (used as module as gauge for table width)
+ setWindowWidthTo(1024);
+ document.querySelector('main').setAttribute('style', 'width: 742px');
+
+ // set total width of column for row numbers in table to 40px
+ rowNumberColumnCell.setAttribute('style', 'width: 40px');
+
+ // start module
+ window.GOVUK.modules.start();
+
+ tableFrame = document.querySelector('.fullscreen-scrollable-table');
+ numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
+
+ });
+
+ afterEach(() => {
+
+ jest.clearAllMocks();
+ resetWindowDimensions();
+ scrollWindowBy(0);
+
+ });
+
+ test("when the page has loaded", () => {
+
+ // table should set its width to be that of ``, minus margin-left for the row numbers column
+ expect(window.getComputedStyle(tableFrame)['width']).toEqual('702px'); // width of content column - numbers column
+ expect(window.getComputedStyle(tableFrame)['margin-left']).toEqual('40px'); // width of numbers column
+
+ // table for number column has 4px extra to allow space for drop shadow
+ expect(window.getComputedStyle(numberColumnFrame)['width']).toEqual('44px');
+
+ });
+
+ test("when the page has resized", () => {
+
+ // resize window and content column
+ document.querySelector('main').setAttribute('style', 'width: 720px');
+ resizeWindowTo({ height: 768, width: 960 });
+
+ // table should set its width to be that of ``, minus margin-left for the row numbers column
+ expect(window.getComputedStyle(tableFrame)['width']).toEqual('680px'); // width of content column - numbers column
+ expect(window.getComputedStyle(tableFrame)['margin-left']).toEqual('40px'); // width of numbers column
+
+ // table for number column has 4px extra to allow space for drop shadow
+ expect(window.getComputedStyle(numberColumnFrame)['width']).toEqual('44px');
+
+ });
+
+ });
+
+ describe("when the table scrolls horizontally", () => {
+ let rightEdgeShadow;
+
+ beforeEach(() => {
+
+ // start module
+ window.GOVUK.modules.start();
+
+ tableFrame = document.querySelector('.fullscreen-scrollable-table');
+ table = tableFrame.querySelector('table');
+ numberColumnFrame = document.querySelector('.fullscreen-fixed-table');
+ rightEdgeShadow = container.querySelector('.fullscreen-right-shadow');
+
+ tableFrame.setAttribute('style', 'width: 742px');
+ table.setAttribute('style', 'width: 1000px');
+
+ });
+
+ test("the right edge of the table scroll area should have a drop-shadow if it isn't scrolled", () => {
+
+ tableFrame.scrollLeft = 0;
+ helpers.triggerEvent(tableFrame, 'scroll');
+
+ expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(false);
+ expect(rightEdgeShadow.classList.contains('visible')).toBe(true);
+
+ });
+
+ test("the left edge of the table scroll area should have a drop-shadow if the table is scrolled to 100%", () => {
+
+ // scroll to end of table
+ tableFrame.scrollLeft = 258;
+ helpers.triggerEvent(tableFrame, 'scroll');
+
+ expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(true);
+ expect(rightEdgeShadow.classList.contains('visible')).toBe(false);
+
+ });
+
+ test("both edges of the table scroll area should have a drop-shadow if the table is scrolled between 0% and 100%", () => {
+
+ // scroll to middle of table
+ tableFrame.scrollLeft = 129;
+ helpers.triggerEvent(tableFrame, 'scroll');
+
+ expect(numberColumnFrame.classList.contains('fullscreen-scrolled-table')).toBe(true);
+ expect(rightEdgeShadow.classList.contains('visible')).toBe(true);
+
+ });
+
+ });
+
+});