Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
run: pnpm -v
- name: Run pnpm CI install
run: pnpm install
- name: Run type check
run: pnpm run type-check:all
- name: Run test
run: pnpm run test-ci
- name: Upload test results
Expand Down
13 changes: 9 additions & 4 deletions cypress/e2e/game/misc.cy.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/// <reference types="cypress" />

import type { CyHttpMessages } from "cypress/types/net-stubbing";
import { GamePersistentState, GameState } from "../../../src/game";

const MOBILE_DEVICE_USER_AGENT =
"Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1";
// @ts-ignore Currently unused
const DESKTOP_USER_AGENT =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:107.0) Gecko/20100101 Firefox/107.0";

Expand All @@ -16,7 +18,10 @@ describe("misc", () => {
unlockables: {},
hasPlayedBefore: true,
};
window.localStorage.setItem("2048-persistent-state", JSON.stringify(persistentState));
window.localStorage.setItem(
"2048-persistent-state",
JSON.stringify(persistentState),
);
},
});
});
Expand Down Expand Up @@ -125,7 +130,7 @@ describe("misc", () => {
window.localStorage.setItem("2048-game-state", JSON.stringify(gameState));
window.localStorage.setItem(
"2048-persistent-state",
JSON.stringify(persistentState)
JSON.stringify(persistentState),
);
},
}); // visit the page
Expand All @@ -152,7 +157,7 @@ describe("misc", () => {
"2048-preferences",
JSON.stringify({
theme: "snow",
})
}),
);
},
});
Expand Down Expand Up @@ -248,7 +253,7 @@ describe("misc", () => {
});

it("should only make one request to the changelog", () => {
const interceptedRequests = [];
const interceptedRequests: CyHttpMessages.IncomingHttpRequest[] = [];

// Intercept network requests to /CHANGELOG.html
cy.intercept("GET", "/CHANGELOG.html", (req) => {
Expand Down
24 changes: 18 additions & 6 deletions cypress/e2e/game/settings.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ describe("settings", () => {

cy.get(".settings-item.animations .knob").should("not.have.class", "enabled");
cy.window().then((win) => {
const storedPreferences = win.localStorage.getItem("2048-preferences");
expect(storedPreferences).to.not.be.null;
// TODO: In production, the two debug hud options will not be enabled,
// AFAIK, there is no way to turn off Vite dev mode for just one test, so this will have to do for now.
expect(JSON.parse(win.localStorage.getItem("2048-preferences"))).to.deep.equal(
expect(JSON.parse(storedPreferences || "{}")).to.deep.equal(
{
theme: "dark",
debugHudEnabled: "enabled",
Expand All @@ -88,7 +90,9 @@ describe("settings", () => {

cy.get(".settings-item.animations .knob").should("have.class", "enabled");
cy.window().then((win) => {
expect(JSON.parse(win.localStorage.getItem("2048-preferences"))).to.deep.equal(
const storedPreferences = win.localStorage.getItem("2048-preferences");
expect(storedPreferences).to.not.be.null;
expect(JSON.parse(storedPreferences || "{}")).to.deep.equal(
{
theme: "dark",
animations: "enabled",
Expand All @@ -102,7 +106,9 @@ describe("settings", () => {

cy.get(".settings-item.animations .knob").should("not.have.class", "enabled");
cy.window().then((win) => {
expect(JSON.parse(win.localStorage.getItem("2048-preferences"))).to.deep.equal(
const storedPreferences = win.localStorage.getItem("2048-preferences");
expect(storedPreferences).to.not.be.null;
expect(JSON.parse(storedPreferences || "{}")).to.deep.equal(
{
theme: "dark",
animations: "disabled",
Expand Down Expand Up @@ -185,9 +191,11 @@ describe("settings", () => {

cy.get(".settings-item.animations .knob").should("not.have.class", "enabled");
cy.window().then((win) => {
const storedPreferences = win.localStorage.getItem("2048-preferences");
expect(storedPreferences).to.not.be.null;
// The invalid value should be replaced with the default value,
// which will be set to debug hud options in dev mode
expect(JSON.parse(win.localStorage.getItem("2048-preferences"))).to.deep.equal(
expect(JSON.parse(storedPreferences || "{}")).to.deep.equal(
{
debugHudEnabled: "enabled",
debugHudVisible: "enabled",
Expand All @@ -206,7 +214,9 @@ describe("settings", () => {

cy.get(".settings-item.animations .knob").should("have.class", "enabled");
cy.window().then((win) => {
expect(JSON.parse(win.localStorage.getItem("2048-preferences"))).to.deep.equal(
const storedPreferences = win.localStorage.getItem("2048-preferences");
expect(storedPreferences).to.not.be.null;
expect(JSON.parse(storedPreferences || "{}")).to.deep.equal(
{
animations: "enabled",
debugHudEnabled: "enabled",
Expand All @@ -219,7 +229,9 @@ describe("settings", () => {

cy.get(".settings-item.animations .knob").should("not.have.class", "enabled");
cy.window().then((win) => {
expect(JSON.parse(win.localStorage.getItem("2048-preferences"))).to.deep.equal(
const storedPreferences = win.localStorage.getItem("2048-preferences");
expect(storedPreferences).to.not.be.null;
expect(JSON.parse(storedPreferences || "{}")).to.deep.equal(
{
animations: "disabled",
debugHudEnabled: "enabled",
Expand Down
57 changes: 32 additions & 25 deletions cypress/e2e/game/share.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ describe("sharing results", () => {
theme: "dark",
};
window.localStorage.setItem("2048-game-state", JSON.stringify(gameState));
window.localStorage.setItem("2048-persistent-state", JSON.stringify(persistentState));
window.localStorage.setItem(
"2048-persistent-state",
JSON.stringify(persistentState),
);
window.localStorage.setItem("2048-preferences", JSON.stringify(preferences));
},
});
Expand All @@ -44,7 +47,7 @@ describe("sharing results", () => {
const stubShare = () => {
cy.window().then((win) => {
// Return false so that it will fallback to clipboard option
const canShareFunc = (data) => false;
const canShareFunc = (_data: ShareData) => false;
// Create the property for canShare if it doesn't exist (ie. browser does not support share sheet), otherwise stub directly
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
Expand All @@ -62,13 +65,14 @@ describe("sharing results", () => {
describe("share sheet method", () => {
it("sends data to share sheet", (done) => {
const PREV_COPIED_TEXT = "This data should still be in clipboard when share is clicked";
let shareStub;
// NOTE: This initial stub is only set to infer the type (Cypress's built-in SinonStub type is incompatible with SinonStub from sinon type package)
let shareStub = cy.stub().resolves();

cy.window().then(async (win) => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
Expand Down Expand Up @@ -119,13 +123,14 @@ describe("sharing results", () => {

it("sends data to share sheet with 2048 achievement", (done) => {
const PREV_COPIED_TEXT = "This data should still be in clipboard when share is clicked";
let shareStub;
// NOTE: This initial stub is only set to infer the type (Cypress's built-in SinonStub type is incompatible with SinonStub from sinon type package)
let shareStub = cy.stub().resolves();

cy.window().then(async (win) => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
Expand Down Expand Up @@ -176,13 +181,14 @@ describe("sharing results", () => {

it("sends data to share sheet with multiple moves", (done) => {
const PREV_COPIED_TEXT = "This data should still be in clipboard when share is clicked";
let shareStub;
// NOTE: This initial stub is only set to infer the type (Cypress's built-in SinonStub type is incompatible with SinonStub from sinon type package)
let shareStub = cy.stub().resolves();

cy.window().then(async (win) => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
Expand Down Expand Up @@ -234,13 +240,14 @@ describe("sharing results", () => {

it("sends data to share sheet with move count reflecting undos that may have been made", (done) => {
const PREV_COPIED_TEXT = "This data should still be in clipboard when share is clicked";
let shareStub;
// NOTE: This initial stub is only set to infer the type (Cypress's built-in SinonStub type is incompatible with SinonStub from sinon type package)
let shareStub = cy.stub().resolves();

cy.window().then(async (win) => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
Expand Down Expand Up @@ -301,12 +308,12 @@ describe("sharing results", () => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
}
const shareFunc = (data) => {
const shareFunc = (data: ShareData) => {
console.log("Shared data:", data);
// Return a Promise to simulate cancelling share operation
return Promise.reject(new DOMException("Share canceled", "AbortError"));
Expand Down Expand Up @@ -351,16 +358,16 @@ describe("sharing results", () => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
}
const shareFunc = (data) => {
const shareFunc = (data: ShareData) => {
console.log("Shared data:", data);
// Return a Promise to simulate permission issue
return Promise.reject(
new DOMException("User or app denied permission", "NotAllowedError")
new DOMException("User or app denied permission", "NotAllowedError"),
);
};
// Define share function as a stub if it's a browser that doesn't support it normally, otherwise stub it directly
Expand Down Expand Up @@ -391,7 +398,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`,
);
});

Expand All @@ -405,12 +412,12 @@ describe("sharing results", () => {
// Create the property for canShare if it doesn't exist
if (!win.navigator.canShare) {
Object.defineProperty(win.navigator, "canShare", {
value: cy.stub().callsFake((data) => true),
value: cy.stub().callsFake((_data: ShareData) => true),
writable: true,
configurable: true,
});
}
const shareFunc = (data) => {
const shareFunc = (data: ShareData) => {
console.log("Shared data:", data);
// Return a Promise to simulate unknown error
return Promise.reject(new DOMException("Unknown error", "UnknownError"));
Expand Down Expand Up @@ -452,7 +459,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`,
);
});
});
Expand All @@ -461,7 +468,7 @@ describe("sharing results", () => {
const PREV_COPIED_TEXT = "This data should still be in clipboard when share is clicked";

cy.window().then(async (win) => {
const canShareFunc = (data) => {
const canShareFunc = (data: ShareData) => {
console.log("Shared data to be validated:", data);
// Return false to indicate that sharing cannot be done due to invalid data
return false;
Expand Down Expand Up @@ -494,7 +501,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`,
);
});

Expand Down Expand Up @@ -531,7 +538,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`
`I got a score of 10000 in 2048-clone in 0 moves. Play it here: ${expectedUrl}`,
);
});
});
Expand Down Expand Up @@ -560,7 +567,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 2048 in 2048-clone, and I achieved 2048 in 1 move. Play it here: ${expectedUrl}`
`I got a score of 2048 in 2048-clone, and I achieved 2048 in 1 move. Play it here: ${expectedUrl}`,
);
});
});
Expand Down Expand Up @@ -590,7 +597,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 4096 in 2048-clone, and I achieved 2048 in 2 moves. Play it here: ${expectedUrl}`
`I got a score of 4096 in 2048-clone, and I achieved 2048 in 2 moves. Play it here: ${expectedUrl}`,
);
});
});
Expand Down Expand Up @@ -624,7 +631,7 @@ describe("sharing results", () => {
cy.window().then(async (win) => {
const copiedText = await win.navigator.clipboard.readText();
expect(copiedText).to.eq(
`I got a score of 4096 in 2048-clone, and I achieved 2048 in 2 moves. Play it here: ${expectedUrl}`
`I got a score of 4096 in 2048-clone, and I achieved 2048 in 2 moves. Play it here: ${expectedUrl}`,
);
});
});
Expand Down
4 changes: 2 additions & 2 deletions cypress/support/commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ Cypress.Commands.add("shouldNotBeActionable", { prevSubject: "element" }, (subje
Cypress.Commands.add("shouldBeInViewport", { prevSubject: true }, (subject) => {
// @ts-ignore TODO: Fix cy.state type error
const window = Cypress.$(cy.state("window"));
const bottom = window.height();
const right = window.width();
const bottom = window.height() ?? 0;
const right = window.width() ?? 0;
const rect = subject[0].getBoundingClientRect();

expect(rect.top).not.to.be.greaterThan(bottom).and.not.to.be.lessThan(0);
Expand Down
4 changes: 2 additions & 2 deletions cypress/support/commands/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Cypress.Commands.add("verifyBoardMatches", (expectedBoard: (number | undefined)[
expect(boxes).to.have.length(expectedBoard[i].length);
for (let j = 0; j < expectedBoard[i].length; j++) {
const expectedVal = expectedBoard[i][j];
if (expectedVal > 0) {
if (expectedVal && expectedVal > 0) {
expect(boxes.eq(j)).to.have.text(expectedVal.toString());
} else if (expectedVal === 0) {
expect(boxes.eq(j)).to.have.text("");
Expand All @@ -33,7 +33,7 @@ Cypress.Commands.add("verifyBoardDoesNotMatch", (expectedBoard: (number | undefi
for (let j = 0; j < expectedBoard[i].length; j++) {
const expectedVal = expectedBoard[i][j];
const boxElem = boxes.eq(j);
if (boxElem.text() === expectedVal.toString()) {
if (expectedVal && boxElem.text() === expectedVal.toString()) {
numMatches++;
}
}
Expand Down
6 changes: 2 additions & 4 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress", "node", "cypress-real-events"],
"resolveJsonModule": true
"types": ["cypress", "node", "cypress-real-events"]
},
"include": ["**/*.ts"]
}
Loading
Loading