From dae1fc40c1e8048249363614b34f373189a8e320 Mon Sep 17 00:00:00 2001 From: Nikos Katsikanis Date: Sat, 26 Oct 2024 20:04:21 +0200 Subject: [PATCH] unit test for theme switcher --- js/components/2d-road.js | 50 ----------- js/components/3d-road.js | 125 --------------------------- js/components/theme-switcher.js | 11 +-- js/components/theme-switcher.test.js | 70 +++++++++++++++ vitest.config.js | 2 +- 5 files changed, 74 insertions(+), 184 deletions(-) delete mode 100644 js/components/2d-road.js delete mode 100644 js/components/3d-road.js create mode 100644 js/components/theme-switcher.test.js diff --git a/js/components/2d-road.js b/js/components/2d-road.js deleted file mode 100644 index 88c9024..0000000 --- a/js/components/2d-road.js +++ /dev/null @@ -1,50 +0,0 @@ -export default (hostComponent) => { - // Create a canvas and get the 2D context - const canvas = document.createElement('canvas'); - hostComponent.appendChild(canvas); - const ctx = canvas.getContext('2d'); - - // Function to resize canvas - const resizeCanvas = () => { - canvas.width = hostComponent.clientWidth; - canvas.height = hostComponent.clientHeight; - }; - - // Initially resize the canvas - resizeCanvas(); - - // Resize the canvas whenever the window size changes - addEventListener('resize', resizeCanvas); - - // Draw the initial state of the road - const drawRoad = (offsetY) => { - // Clear the canvas - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Draw the road - ctx.fillStyle = 'black'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Draw the road markings - const markingHeight = 20; - const markingSpacing = 40; - const totalMarkings = Math.ceil(canvas.height / (markingHeight + markingSpacing)) + 1; - - ctx.fillStyle = 'white'; - for (let i = 0; i < totalMarkings; i++) { - const y = i * (markingHeight + markingSpacing) - offsetY; - ctx.fillRect(canvas.width / 2 - 2, y, 4, markingHeight); - } - }; - - // Animate the road - let offsetY = 0; - const animateRoad = () => { - drawRoad(offsetY); - offsetY = (offsetY + 2) % 60; // Increase speed by increasing the '2' here - requestAnimationFrame(animateRoad); - }; - - // Start the animation - animateRoad(); -}; diff --git a/js/components/3d-road.js b/js/components/3d-road.js deleted file mode 100644 index 49f6d2f..0000000 --- a/js/components/3d-road.js +++ /dev/null @@ -1,125 +0,0 @@ -export default (hostComponent) => { - const canvas = hostComponent.appendChild(document.createElement('canvas')); - const gl = canvas.getContext('webgl'); - - // Define the positions for the vertices - const positions = [ - -1.0, - -1.0, - -5.0, // Back left - 1.0, - -1.0, - -5.0, // Back right - -1.0, - -1.0, - 5.0, // Front left - 1.0, - -1.0, - 5.0, // Front right - ]; - - const positionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW); - - // Vertex shader program - const vsSource = ` - attribute vec3 aVertexPosition; - uniform mat4 uProjectionMatrix; - void main() { - gl_Position = uProjectionMatrix * vec4(aVertexPosition, 1.0); - } - `; - - // Fragment shader program - const fsSource = ` - void main() { - gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); - } - `; - - // Create shader program - const shaderProgram = createShaderProgram(gl, vsSource, fsSource); - gl.useProgram(shaderProgram); - - // Look up where the vertex data needs to go. - const positionLocation = gl.getAttribLocation(shaderProgram, 'aVertexPosition'); - gl.enableVertexAttribArray(positionLocation); - gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); - gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0); - - // Set the clear color to black - gl.clearColor(0.0, 0.0, 0.0, 1.0); - gl.clear(gl.COLOR_BUFFER_BIT); - - // Compute the projection matrix - const fieldOfView = (45 * Math.PI) / 180; // in radians - const aspect = canvas.clientWidth / canvas.clientHeight; - const zNear = 0.1; - const zFar = 100.0; - const projectionMatrix = perspective(fieldOfView, aspect, zNear, zFar); - - // Set the projection matrix - const projectionMatrixLocation = gl.getUniformLocation(shaderProgram, 'uProjectionMatrix'); - gl.uniformMatrix4fv(projectionMatrixLocation, false, projectionMatrix); - - // Draw the road - gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); -}; - -// Function to create shader program -function createShaderProgram(gl, vsSource, fsSource) { - const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource); - const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource); - - const shaderProgram = gl.createProgram(); - gl.attachShader(shaderProgram, vertexShader); - gl.attachShader(shaderProgram, fragmentShader); - gl.linkProgram(shaderProgram); - - if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { - console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram)); - return null; - } - - return shaderProgram; -} - -// Function to create shader -function createShader(gl, type, source) { - const shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); - gl.deleteShader(shader); - return null; - } - - return shader; -} - -// Function to create perspective matrix -function perspective(fieldOfViewInRadians, aspect, near, far) { - var f = Math.tan(Math.PI * 0.5 - 0.5 * fieldOfViewInRadians); - var rangeInv = 1.0 / (near - far); - return [ - f / aspect, - 0, - 0, - 0, - 0, - f, - 0, - 0, - 0, - 0, - (near + far) * rangeInv, - -1, - 0, - 0, - near * far * rangeInv * 2, - 0, - ]; -} diff --git a/js/components/theme-switcher.js b/js/components/theme-switcher.js index 602d8c7..3e62bec 100644 --- a/js/components/theme-switcher.js +++ b/js/components/theme-switcher.js @@ -1,22 +1,17 @@ -// stored in /components/theme-switcher.js export default (hostComponent) => { const themes = ['green', 'red', 'blue', 'grey']; // added 'grey' const themeButtons = themes .map( - (theme) => ` - - `, + (theme) => + ``, ) .join(''); hostComponent.innerHTML = `
- Theme ${themeButtons} + Theme${themeButtons}
- `; themes.forEach((theme) => { diff --git a/js/components/theme-switcher.test.js b/js/components/theme-switcher.test.js new file mode 100644 index 0000000..dbcb9ff --- /dev/null +++ b/js/components/theme-switcher.test.js @@ -0,0 +1,70 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import themeSwitcher from './theme-switcher'; + +// Mocking localStorage and document.documentElement for the test environment +const mockLocalStorage = (() => { + let store = {}; + return { + getItem: (key) => store[key] || null, + setItem: (key, value) => (store[key] = value), + clear: () => (store = {}), + }; +})(); + +beforeEach(() => { + vi.stubGlobal('localStorage', mockLocalStorage); + document.documentElement.className = ''; // Reset class list on document element +}); + +describe('themeSwitcher', () => { + it('renders theme buttons correctly', () => { + const hostComponent = document.createElement('div'); + themeSwitcher(hostComponent); + + // Check that each theme button is rendered + const buttons = hostComponent.querySelectorAll('button[data-theme]'); + expect(buttons.length).toBe(4); // green, red, blue, grey + + const themes = ['Green', 'Red', 'Blue', 'Grey']; + themes.forEach((theme, index) => { + expect(buttons[index].textContent).toBe(theme); + }); + }); + + it('applies the theme class to document.documentElement on button click', () => { + const hostComponent = document.createElement('div'); + themeSwitcher(hostComponent); + + // Simulate clicking the 'red' theme button + const redButton = hostComponent.querySelector('button[data-theme="red"]'); + redButton.click(); + + expect(document.documentElement.classList.contains('red-theme')).toBe(true); + + // Ensure other themes are not active + expect(document.documentElement.classList.contains('green-theme')).toBe(false); + expect(document.documentElement.classList.contains('blue-theme')).toBe(false); + expect(document.documentElement.classList.contains('grey-theme')).toBe(false); + }); + + it('stores the selected theme in localStorage on button click', () => { + const hostComponent = document.createElement('div'); + themeSwitcher(hostComponent); + + // Click the 'blue' theme button + const blueButton = hostComponent.querySelector('button[data-theme="blue"]'); + blueButton.click(); + + expect(localStorage.getItem('theme')).toBe('blue'); + }); + + it('loads and applies the saved theme from localStorage on initialization', () => { + localStorage.setItem('theme', 'grey'); // Simulate a previously selected theme + + const hostComponent = document.createElement('div'); + themeSwitcher(hostComponent); + + // Ensure the saved theme is applied on load + expect(document.documentElement.classList.contains('grey-theme')).toBe(true); + }); +}); diff --git a/vitest.config.js b/vitest.config.js index 7fd3c7d..1eb3ed8 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -10,7 +10,7 @@ export default defineConfig({ coverage: { reporter: ['text-summary'], // Only display the summary report provider: 'v8', // Use the v8 provider for coverage - exclude: ['node_modules/'], + exclude: ['node_modules/','examples/'], // Exclude node_modules and Vue project from coverage }, }, });