diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22a22e1a..6104d1d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,6 +34,7 @@ jobs: continue-on-error: true with: run: npm run test:coverage - # - uses: codecov/codecov-action@v3 - # with: - # directory: ./coverage \ No newline at end of file + - uses: codecov/codecov-action@v3 + with: + directory: ./coverage + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/media/main-bundle.js b/media/main-bundle.js index 00c30528..98a79554 100644 --- a/media/main-bundle.js +++ b/media/main-bundle.js @@ -112,6 +112,13 @@ exports.CAT_NAMES = new Map([ [98, 'Daisy'], [99, 'Amelia'], [100, 'Oliver'], + [101, 'Ghost'], + [102, 'Midnight'], + [103, 'Pumpkin'], + [105, 'Shadow'], + [106, 'Binx'], + [107, 'Riley'], + [108, 'Lenny'], ]); exports.DOG_NAMES = new Map([ [1, 'Bella'], @@ -214,6 +221,11 @@ exports.DOG_NAMES = new Map([ [98, 'Bolt'], [99, 'Ein'], [100, 'Maddy'], + [101, 'Ghost'], + [102, 'Midnight'], + [103, 'Pumpkin'], + [105, 'Shadow'], + [106, 'Sparky'], ]); exports.CRAB_NAMES = new Map([ [1, 'Ferris'], @@ -385,7 +397,6 @@ exports.petPanelApp = exports.saveState = exports.allPets = void 0; const names_1 = __webpack_require__(/*! ../common/names */ "./src/common/names.ts"); const pets_1 = __webpack_require__(/*! ./pets */ "./src/panel/pets.ts"); const states_1 = __webpack_require__(/*! ./states */ "./src/panel/states.ts"); -const vscode = window.acquireVsCodeApi(); exports.allPets = new pets_1.PetCollection(); var petCounter; function calculateBallRadius(size) { @@ -448,21 +459,24 @@ function handleMouseOver(e) { } }); } -function startAnimations(collision, pet) { +function startAnimations(collision, pet, stateApi) { + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } collision.addEventListener('mouseover', handleMouseOver); setInterval(() => { var updates = exports.allPets.seekNewFriends(); updates.forEach((message) => { - vscode.postMessage({ + stateApi?.postMessage({ text: message, command: 'info', }); }); pet.nextFrame(); - saveState(); + saveState(stateApi); }, 100); } -function addPetToPanel(petType, basePetUri, petColor, petSize, left, bottom, floor, name) { +function addPetToPanel(petType, basePetUri, petColor, petSize, left, bottom, floor, name, stateApi) { var petSpriteElement = document.createElement('img'); petSpriteElement.className = 'pet'; document.getElementById('petsContainer').appendChild(petSpriteElement); @@ -478,7 +492,7 @@ function addPetToPanel(petType, basePetUri, petColor, petSize, left, bottom, flo try { var newPet = (0, pets_1.createPet)(petType, petSpriteElement, collisionElement, speechBubbleElement, petSize, left, bottom, root, floor, name); petCounter++; - startAnimations(collisionElement, newPet); + startAnimations(collisionElement, newPet, stateApi); } catch (e) { // Remove elements @@ -489,7 +503,10 @@ function addPetToPanel(petType, basePetUri, petColor, petSize, left, bottom, flo } return new pets_1.PetElement(petSpriteElement, collisionElement, speechBubbleElement, newPet, petColor, petType); } -function saveState() { +function saveState(stateApi) { + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } var state = new states_1.PetPanelState(); state.petStates = new Array(); exports.allPets.pets().forEach((petItem) => { @@ -504,25 +521,33 @@ function saveState() { }); }); state.petCounter = petCounter; - vscode.setState(state); + stateApi.setState(state); } exports.saveState = saveState; -function recoverState(basePetUri, petSize, floor) { - var state = vscode.getState(); - if (state.petCounter === undefined || isNaN(state.petCounter)) { +function recoverState(basePetUri, petSize, floor, stateApi) { + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } + var state = stateApi.getState(); + if (!state) { petCounter = 1; } else { - petCounter = state.petCounter ?? 1; + if (state.petCounter === undefined || isNaN(state.petCounter)) { + petCounter = 1; + } + else { + petCounter = state.petCounter ?? 1; + } } var recoveryMap = new Map(); - state.petStates?.forEach((p) => { + state?.petStates?.forEach((p) => { // Fixes a bug related to duck animations if (p.petType === 'rubber duck') { p.petType = 'rubber-duck'; } try { - var newPet = addPetToPanel(p.petType ?? "cat" /* PetType.cat */, basePetUri, p.petColor ?? "brown" /* PetColor.brown */, petSize, parseInt(p.elLeft ?? '0'), parseInt(p.elBottom ?? '0'), floor, p.petName ?? (0, names_1.randomName)(p.petType ?? "cat" /* PetType.cat */)); + var newPet = addPetToPanel(p.petType ?? "cat" /* PetType.cat */, basePetUri, p.petColor ?? "brown" /* PetColor.brown */, petSize, parseInt(p.elLeft ?? '0'), parseInt(p.elBottom ?? '0'), floor, p.petName ?? (0, names_1.randomName)(p.petType ?? "cat" /* PetType.cat */), stateApi); exports.allPets.push(newPet); recoveryMap.set(newPet.pet, p); } @@ -551,14 +576,25 @@ function randomStartPosition() { let canvas, ctx; function initCanvas() { canvas = document.getElementById('petCanvas'); + if (!canvas) { + console.log('Canvas not ready'); + return; + } ctx = canvas.getContext('2d'); + if (!ctx) { + console.log('Canvas context not ready'); + return; + } ctx.canvas.width = window.innerWidth; ctx.canvas.height = window.innerHeight; } // It cannot access the main VS Code APIs directly. -function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { +function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType, stateApi) { const ballRadius = calculateBallRadius(petSize); var floor = 0; + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } // Apply Theme backgrounds const foregroundEl = document.getElementById('foreground'); if (theme !== "none" /* Theme.none */) { @@ -590,7 +626,9 @@ function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { let then = 0; // last draw var ballState; function resetBall() { - canvas.style.display = 'block'; + if (canvas) { + canvas.style.display = 'block'; + } ballState = new states_1.BallState(100, 100, 4, 5); } function throwBall() { @@ -633,16 +671,16 @@ function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { } console.log('Starting pet session', petColor, basePetUri, petType); // New session - var state = vscode.getState(); + var state = stateApi.getState(); if (!state) { console.log('No state, starting a new session.'); petCounter = 1; - exports.allPets.push(addPetToPanel(petType, basePetUri, petColor, petSize, randomStartPosition(), floor, floor, (0, names_1.randomName)(petType))); - saveState(); + exports.allPets.push(addPetToPanel(petType, basePetUri, petColor, petSize, randomStartPosition(), floor, floor, (0, names_1.randomName)(petType), stateApi)); + saveState(stateApi); } else { console.log('Recovering state - ', state); - recoverState(basePetUri, petSize, floor); + recoverState(basePetUri, petSize, floor, stateApi); } initCanvas(); // Handle messages sent from the extension to the webview @@ -659,12 +697,12 @@ function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { }); break; case 'spawn-pet': - exports.allPets.push(addPetToPanel(message.type, basePetUri, message.color, petSize, randomStartPosition(), floor, floor, message.name ?? (0, names_1.randomName)(message.type))); - saveState(); + exports.allPets.push(addPetToPanel(message.type, basePetUri, message.color, petSize, randomStartPosition(), floor, floor, message.name ?? (0, names_1.randomName)(message.type), stateApi)); + saveState(stateApi); break; case 'list-pets': var pets = exports.allPets.pets(); - vscode.postMessage({ + stateApi?.postMessage({ command: 'list-pets', text: pets .map((pet) => `${pet.type},${pet.pet.name()},${pet.color}`) @@ -676,7 +714,7 @@ function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { // go through every single // pet and then print out their name pets.forEach((pet) => { - vscode.postMessage({ + stateApi?.postMessage({ command: 'info', text: `${pet.pet.emoji()} ${pet.pet.name()} (${pet.color} ${pet.type}): ${pet.pet.hello()}`, }); @@ -685,14 +723,14 @@ function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { var pet = exports.allPets.locate(message.name); if (pet) { exports.allPets.remove(message.name); - saveState(); - vscode.postMessage({ + saveState(stateApi); + stateApi?.postMessage({ command: 'info', text: '👋 Removed pet ' + message.name, }); } else { - vscode.postMessage({ + stateApi?.postMessage({ command: 'error', text: `Could not find pet ${message.name}`, }); @@ -701,11 +739,11 @@ function petPanelApp(basePetUri, theme, themeKind, petColor, petSize, petType) { case 'reset-pet': exports.allPets.reset(); petCounter = 0; - saveState(); + saveState(stateApi); break; case 'pause-pet': petCounter = 1; - saveState(); + saveState(stateApi); break; } }); @@ -1386,8 +1424,7 @@ class Clippy extends BasePetType { return '📎'; } hello() { - // TODO: #188 Add a custom message for clippy - return ` says hello 👋!`; + return ` Hi, I'm Clippy, would you like some assistance today? 👋!`; } } exports.Clippy = Clippy; diff --git a/package-lock.json b/package-lock.json index 4d58c39f..004aa4a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.14.0", "license": "MIT", "devDependencies": { - "@istanbuljs/nyc-config-typescript": "^1.0.2", "@rbarilani/remove-source-map-url-webpack-plugin": "^1.1.0", "@types/glob": "^7.1.3", "@types/jsdom": "^20.0.0", @@ -23,6 +22,7 @@ "glob": "^8.0.3", "gulp": "^4.0.2", "jsdom": "^20.0.0", + "jsdom-global": "^3.0.2", "mocha": "^10.0.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -598,21 +598,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/nyc-config-typescript": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", - "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "nyc": ">=15" - } - }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -5448,6 +5433,15 @@ } } }, + "node_modules/jsdom-global": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz", + "integrity": "sha512-t1KMcBkz/pT5JrvcJbpUR2u/w1kO9jXctaaGJ0vZDzwFnIvGWw9IDSRciT83kIs8Bnw4qpOl8bQK08V01YgMPg==", + "dev": true, + "peerDependencies": { + "jsdom": ">=10.0.0" + } + }, "node_modules/jsdom/node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -9882,15 +9876,6 @@ } } }, - "@istanbuljs/nyc-config-typescript": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-1.0.2.tgz", - "integrity": "sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2" - } - }, "@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -13709,6 +13694,13 @@ } } }, + "jsdom-global": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz", + "integrity": "sha512-t1KMcBkz/pT5JrvcJbpUR2u/w1kO9jXctaaGJ0vZDzwFnIvGWw9IDSRciT83kIs8Bnw4qpOl8bQK08V01YgMPg==", + "dev": true, + "requires": {} + }, "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", diff --git a/package.json b/package.json index aab00dde..393d6b36 100644 --- a/package.json +++ b/package.json @@ -189,10 +189,9 @@ "lint": "eslint src --ext ts && prettier --check src", "lint:fix": "eslint src --ext ts --fix && prettier --write src", "test": "node ./out/test/runTest.js", - "test:coverage": "nyc npm run test" + "test:coverage": "COVERAGE=1 node ./out/test/runTest.js" }, "devDependencies": { - "@istanbuljs/nyc-config-typescript": "^1.0.2", "@rbarilani/remove-source-map-url-webpack-plugin": "^1.1.0", "@types/glob": "^7.1.3", "@types/jsdom": "^20.0.0", @@ -206,6 +205,7 @@ "glob": "^8.0.3", "gulp": "^4.0.2", "jsdom": "^20.0.0", + "jsdom-global": "^3.0.2", "mocha": "^10.0.0", "nyc": "^15.1.0", "prettier": "^2.7.1", @@ -216,23 +216,5 @@ "vscode-test": "^1.4.0", "webpack": "^5.73.0", "webpack-cli": "^4.10.0" - }, - "nyc": { - "extends": "@istanbuljs/nyc-config-typescript", - "check-coverage": true, - "all": true, - "include": [ - "src/**/!(*.test.*).[tj]s?(x)" - ], - "exclude": [ - "src/_tests_/**/*.*" - ], - "reporter": [ - "html", - "lcov", - "text", - "text-summary" - ], - "report-dir": "coverage" } } diff --git a/src/common/types.ts b/src/common/types.ts index 89e9f6f9..c5ce047d 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -65,3 +65,28 @@ export class WebviewMessage { this.command = command; } } + +export const ALL_PETS = [ + PetType.cat, + PetType.clippy, + PetType.cockatiel, + PetType.crab, + PetType.dog, + PetType.rocky, + PetType.rubberduck, + PetType.snake, + PetType.totoro, + PetType.zappy, +]; +export const ALL_COLORS = [ + PetColor.black, + PetColor.brown, + PetColor.green, + PetColor.yellow, + PetColor.gray, + PetColor.red, + PetColor.white, + PetColor.null, +]; +export const ALL_SCALES = [PetSize.nano, PetSize.medium, PetSize.large]; +export const ALL_THEMES = [Theme.none, Theme.forest, Theme.castle, Theme.beach]; diff --git a/src/extension/extension.ts b/src/extension/extension.ts index f538eb75..293fc65f 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -8,6 +8,10 @@ import { ExtPosition, Theme, WebviewMessage, + ALL_COLORS, + ALL_PETS, + ALL_SCALES, + ALL_THEMES, } from '../common/types'; import { randomName } from '../common/names'; import * as localize from '../common/localize'; @@ -22,31 +26,6 @@ const DEFAULT_PET_TYPE = PetType.cat; const DEFAULT_POSITION = ExtPosition.panel; const DEFAULT_THEME = Theme.none; -const ALL_PETS = [ - PetType.cat, - PetType.clippy, - PetType.cockatiel, - PetType.crab, - PetType.dog, - PetType.rocky, - PetType.rubberduck, - PetType.snake, - PetType.totoro, - PetType.zappy, -]; -const ALL_COLORS = [ - PetColor.black, - PetColor.brown, - PetColor.green, - PetColor.yellow, - PetColor.gray, - PetColor.red, - PetColor.white, - PetColor.null, -]; -const ALL_SCALES = [PetSize.nano, PetSize.medium, PetSize.large]; -const ALL_THEMES = [Theme.none, Theme.forest, Theme.castle, Theme.beach]; - class PetQuickPickItem implements vscode.QuickPickItem { constructor( public readonly name_: string, diff --git a/src/panel/main.ts b/src/panel/main.ts index fc7d7467..0516d6a6 100644 --- a/src/panel/main.ts +++ b/src/panel/main.ts @@ -20,7 +20,7 @@ import { BallState, PetElementState, PetPanelState } from './states'; /* This is how the VS Code API can be invoked from the panel */ declare global { interface VscodeStateApi { - getState(): PetPanelState; // API is actually Any, but we want it to be typed. + getState(): PetPanelState | undefined; // API is actually Any, but we want it to be typed. setState(state: PetPanelState): void; postMessage(message: WebviewMessage): void; } @@ -29,8 +29,6 @@ declare global { } } -const vscode = window.acquireVsCodeApi(); - export var allPets: IPetCollection = new PetCollection(); var petCounter: number; @@ -94,18 +92,26 @@ function handleMouseOver(e: MouseEvent) { }); } -function startAnimations(collision: HTMLDivElement, pet: IPetType) { +function startAnimations( + collision: HTMLDivElement, + pet: IPetType, + stateApi?: VscodeStateApi, +) { + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } + collision.addEventListener('mouseover', handleMouseOver); setInterval(() => { var updates = allPets.seekNewFriends(); updates.forEach((message) => { - vscode.postMessage({ + stateApi?.postMessage({ text: message, command: 'info', }); }); pet.nextFrame(); - saveState(); + saveState(stateApi); }, 100); } @@ -118,6 +124,7 @@ function addPetToPanel( bottom: number, floor: number, name: string, + stateApi?: VscodeStateApi, ): PetElement { var petSpriteElement: HTMLImageElement = document.createElement('img'); petSpriteElement.className = 'pet'; @@ -154,7 +161,7 @@ function addPetToPanel( name, ); petCounter++; - startAnimations(collisionElement, newPet); + startAnimations(collisionElement, newPet, stateApi); } catch (e: any) { // Remove elements petSpriteElement.remove(); @@ -173,7 +180,10 @@ function addPetToPanel( ); } -export function saveState() { +export function saveState(stateApi?: VscodeStateApi) { + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } var state = new PetPanelState(); state.petStates = new Array(); @@ -189,20 +199,31 @@ export function saveState() { }); }); state.petCounter = petCounter; - vscode.setState(state); + stateApi.setState(state); } -function recoverState(basePetUri: string, petSize: PetSize, floor: number) { - var state = vscode.getState(); - - if (state.petCounter === undefined || isNaN(state.petCounter)) { +function recoverState( + basePetUri: string, + petSize: PetSize, + floor: number, + stateApi?: VscodeStateApi, +) { + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } + var state = stateApi.getState(); + if (!state) { petCounter = 1; } else { - petCounter = state.petCounter ?? 1; + if (state.petCounter === undefined || isNaN(state.petCounter)) { + petCounter = 1; + } else { + petCounter = state.petCounter ?? 1; + } } var recoveryMap: Map = new Map(); - state.petStates?.forEach((p) => { + state?.petStates?.forEach((p) => { // Fixes a bug related to duck animations if ((p.petType as string) === 'rubber duck') { (p.petType as string) = 'rubber-duck'; @@ -218,6 +239,7 @@ function recoverState(basePetUri: string, petSize: PetSize, floor: number) { parseInt(p.elBottom ?? '0'), floor, p.petName ?? randomName(p.petType ?? PetType.cat), + stateApi, ); allPets.push(newPet); recoveryMap.set(newPet.pet, p); @@ -252,7 +274,15 @@ let canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D; function initCanvas() { canvas = document.getElementById('petCanvas') as HTMLCanvasElement; + if (!canvas) { + console.log('Canvas not ready'); + return; + } ctx = canvas.getContext('2d') as CanvasRenderingContext2D; + if (!ctx) { + console.log('Canvas context not ready'); + return; + } ctx.canvas.width = window.innerWidth; ctx.canvas.height = window.innerHeight; } @@ -265,9 +295,13 @@ export function petPanelApp( petColor: PetColor, petSize: PetSize, petType: PetType, + stateApi?: VscodeStateApi, ) { const ballRadius: number = calculateBallRadius(petSize); var floor = 0; + if (!stateApi) { + stateApi = window.acquireVsCodeApi(); + } // Apply Theme backgrounds const foregroundEl = document.getElementById('foreground'); if (theme !== Theme.none) { @@ -305,7 +339,9 @@ export function petPanelApp( var ballState: BallState; function resetBall() { - canvas.style.display = 'block'; + if (canvas) { + canvas.style.display = 'block'; + } ballState = new BallState(100, 100, 4, 5); } @@ -354,7 +390,7 @@ export function petPanelApp( console.log('Starting pet session', petColor, basePetUri, petType); // New session - var state = vscode.getState(); + var state = stateApi.getState(); if (!state) { console.log('No state, starting a new session.'); petCounter = 1; @@ -368,12 +404,13 @@ export function petPanelApp( floor, floor, randomName(petType), + stateApi, ), ); - saveState(); + saveState(stateApi); } else { console.log('Recovering state - ', state); - recoverState(basePetUri, petSize, floor); + recoverState(basePetUri, petSize, floor, stateApi); } initCanvas(); @@ -402,14 +439,15 @@ export function petPanelApp( floor, floor, message.name ?? randomName(message.type), + stateApi, ), ); - saveState(); + saveState(stateApi); break; case 'list-pets': var pets = allPets.pets(); - vscode.postMessage({ + stateApi?.postMessage({ command: 'list-pets', text: pets .map( @@ -425,7 +463,7 @@ export function petPanelApp( // go through every single // pet and then print out their name pets.forEach((pet) => { - vscode.postMessage({ + stateApi?.postMessage({ command: 'info', text: `${pet.pet.emoji()} ${pet.pet.name()} (${ pet.color @@ -436,13 +474,13 @@ export function petPanelApp( var pet = allPets.locate(message.name); if (pet) { allPets.remove(message.name); - saveState(); - vscode.postMessage({ + saveState(stateApi); + stateApi?.postMessage({ command: 'info', text: '👋 Removed pet ' + message.name, }); } else { - vscode.postMessage({ + stateApi?.postMessage({ command: 'error', text: `Could not find pet ${message.name}`, }); @@ -451,11 +489,11 @@ export function petPanelApp( case 'reset-pet': allPets.reset(); petCounter = 0; - saveState(); + saveState(stateApi); break; case 'pause-pet': petCounter = 1; - saveState(); + saveState(stateApi); break; } }); diff --git a/src/test/suite/index.ts b/src/test/suite/index.ts index abcaa3ff..a74b9a4d 100644 --- a/src/test/suite/index.ts +++ b/src/test/suite/index.ts @@ -1,19 +1,45 @@ import * as path from 'path'; import * as Mocha from 'mocha'; import * as glob from 'glob'; +import { join } from 'path'; + +function setupCoverage() { + const NYC = require('nyc'); + const nyc = new NYC({ + cwd: join(__dirname, '..', '..', '..'), + exclude: ['**/test/**', '.vscode-test/**'], + reporter: ['text', 'html', 'lcov'], + all: true, + instrument: true, + hookRequire: true, + hookRunInContext: true, + hookRunInThisContext: true, + }); + + nyc.reset(); + nyc.wrap(); + + return nyc; +} + +export async function run(): Promise { + const nyc = process.env.COVERAGE ? setupCoverage() : null; -export function run(): Promise { // Create the mocha test const mocha = new Mocha({ ui: 'tdd', color: true, - require: ['ts-node/register', 'source-map-support/register'], + require: [ + 'ts-node/register', + 'source-map-support/register', + 'jsdom-global/register', + ], }); const testsRoot = path.resolve(__dirname, '..'); return new Promise((c, e) => { - glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + glob('**/**.test.js', { cwd: testsRoot }, async (err, files) => { if (err) { return e(err); } @@ -33,6 +59,11 @@ export function run(): Promise { } catch (err) { console.error(err); e(err); + } finally { + if (nyc) { + nyc.writeCoverageFile(); + await nyc.report(); + } } }); }); diff --git a/src/test/suite/panel.test.ts b/src/test/suite/panel.test.ts index c3716923..9d7dc389 100644 --- a/src/test/suite/panel.test.ts +++ b/src/test/suite/panel.test.ts @@ -3,21 +3,70 @@ import * as assert from 'assert'; // You can import and use all API from the 'vscode' module // as well as import your extension to test it import * as vscode from 'vscode'; -import { PetSize, PetType, PetColor } from '../../common/types'; +import { + PetSize, + PetType, + PetColor, + Theme, + ColorThemeKind, + WebviewMessage, + ALL_PETS, +} from '../../common/types'; +import { PetElementState, PetPanelState } from '../../panel/states'; import * as pets from '../../panel/pets'; -import { JSDOM } from 'jsdom'; +function mockPanelWindow() { + const html = + '
'; -declare global { - namespace NodeJS { - interface Global { - document: Document; + var jsdom = require('jsdom'); + var document = new jsdom.JSDOM(html); + var window = document.window; + + global.document = window.document; + global.window = window; + window.console = global.console; +} + +class MockState implements VscodeStateApi { + counter: number = 1; + states: Array | undefined = undefined; + sentMessages: Array = []; + + getState(): PetPanelState | undefined { + if (!this.states) { + return undefined; } + return { + petCounter: this.counter, + petStates: this.states, + }; + } + + // eslint-disable-next-line no-unused-vars + setState(state: PetPanelState): void { + this.counter = state.petCounter ?? this.counter; + this.states = state.petStates ?? this.states; + } + + // eslint-disable-next-line no-unused-vars + postMessage(message: WebviewMessage): void { + this.sentMessages.push(message); + } + + getMessages(): Array { + return this.sentMessages; + } + + reset() { + this.counter = 1; + this.states = undefined; } } -const { window } = new JSDOM(''); -global.document = window.document; +mockPanelWindow(); + +import * as panel from '../../panel/main'; suite('Pets Test Suite', () => { vscode.window.showInformationMessage('Start all tests.'); @@ -66,4 +115,100 @@ suite('Pets Test Suite', () => { collection.remove('Jerry'); assert.strictEqual(collection.locate('Jerry'), undefined); }); + + ALL_PETS.forEach((petType) => { + test( + 'Test panel app initialization with theme and ' + String(petType), + () => { + const mockState = new MockState(); + panel.allPets.reset(); + mockState.reset(); + panel.petPanelApp( + 'https://test.com', + Theme.beach, + ColorThemeKind.dark, + PetColor.black, + PetSize.large, + petType, + mockState, + ); + + assert.notStrictEqual(document.body.style.backgroundImage, ''); + assert.notStrictEqual( + document.getElementById('foreground')?.style + .backgroundImage, + '', + ); + + assert.equal(mockState.getState()?.petStates?.length, 1); + + const firstPet: PetElementState = (mockState.getState() + ?.petStates ?? [])[0]; + assert.equal(firstPet.petType, petType); + assert.equal(firstPet.petColor, PetColor.black); + + const createdPets = panel.allPets.pets(); + assert.notEqual(createdPets.at(0), undefined); + + assert.equal(createdPets.at(0)?.color, PetColor.black); + + /// Cycle 1000 frames + for (var i = 0; i < 1000; i++) { + createdPets.at(0)?.pet.nextFrame(); + assert.notEqual( + createdPets.at(0)?.pet.getState(), + undefined, + ); + } + }, + ); + }); + + test('Test panel app initialization with no theme', () => { + const mockState = new MockState(); + panel.allPets.reset(); + mockState.reset(); + panel.petPanelApp( + 'https://test.com', + Theme.none, + ColorThemeKind.dark, + PetColor.black, + PetSize.large, + PetType.cat, + mockState, + ); + + assert.strictEqual(document.body.style.backgroundImage, ''); + assert.strictEqual( + document.getElementById('foreground')?.style.backgroundImage, + '', + ); + }); + + test('Test post message to panel', () => { + const mockState = new MockState(); + panel.allPets.reset(); + mockState.reset(); + panel.petPanelApp( + 'https://test.com', + Theme.none, + ColorThemeKind.dark, + PetColor.black, + PetSize.large, + PetType.cat, + mockState, + ); + + assert.strictEqual(document.body.style.backgroundImage, ''); + assert.strictEqual( + document.getElementById('foreground')?.style.backgroundImage, + '', + ); + const message = new MessageEvent('command', { + data: { message: 'roll-call' }, + }); + window.postMessage(message, '/'); + + // assert.notEqual(mockState.getMessages().length, 0); + }); });