diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..4cf897b
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+/.expo
+node_modules
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..fe5b366
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,8 @@
+// https://docs.expo.dev/guides/using-eslint/
+module.exports = {
+ extends: ["expo", "prettier"],
+ plugins: ["prettier"],
+ rules: {
+ "prettier/prettier": "error",
+ },
+};
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..46773ab
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,35 @@
+name: CI
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+jobs:
+ lint-and-test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Find yarn cache location
+ id: yarn-cache
+ run: echo "::set-output name=dir::$(yarn cache dir)"
+
+ - name: JS package cache
+ uses: actions/cache@v1
+ with:
+ path: $(( steps.yarn-cache.outputs.dir ))
+ key: $(( runner.os ))-yarn-$(( hashFiles('**/yarn.lock') ))
+ restore-keys: |
+ $(( runner.os ))-yarn-
+
+ - name: Install Node Modules
+ run: yarn install
+
+ - name: Run Lint
+ run: yarn lint
+
+ - name: Run tests
+ run: yarn testFinal
diff --git a/__tests__/App.test.tsx b/__tests__/App.test.tsx
index 4b7e7b1..740a1ae 100644
--- a/__tests__/App.test.tsx
+++ b/__tests__/App.test.tsx
@@ -1,14 +1,14 @@
-import renderer from "react-test-renderer";
-import App from "../App";
-
-describe("", () => {
- it("has 1 child", () => {
- const tree: any = renderer.create().toJSON();
- expect(tree.children.length).toBe(1);
- });
-
- it("renders correctly", () => {
- const tree = renderer.create().toJSON();
- expect(tree).toMatchSnapshot();
- });
-});
+import renderer from "react-test-renderer";
+import App from "../App";
+
+describe("", () => {
+ it("has 1 child", () => {
+ const tree: any = renderer.create().toJSON();
+ expect(tree.children.length).toBe(1);
+ });
+
+ it("renders correctly", () => {
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+});
diff --git a/__tests__/__mocks__/boardData.mock.ts b/__tests__/__mocks__/boardData.mock.ts
index f2b5733..1ac0400 100644
--- a/__tests__/__mocks__/boardData.mock.ts
+++ b/__tests__/__mocks__/boardData.mock.ts
@@ -1,6 +1,6 @@
-import { BOARD_SIZES } from "../../src/constants/common";
-import { createBoard } from "../../src/utils/common";
-
-const board = createBoard(BOARD_SIZES._3x3);
-
-export default board;
+import { BOARD_SIZES } from "../../src/constants/common";
+import { createBoard } from "../../src/utils/common";
+
+const board = createBoard(BOARD_SIZES._3x3);
+
+export default board;
diff --git a/__tests__/components/Board.test.tsx b/__tests__/components/Board.test.tsx
index dc51d64..d08ba01 100644
--- a/__tests__/components/Board.test.tsx
+++ b/__tests__/components/Board.test.tsx
@@ -1,94 +1,94 @@
-import React from "react";
-import renderer from "react-test-renderer";
-import Board from "../../src/components/Board";
-import board from "../__mocks__/boardData.mock";
-import { render, screen, userEvent } from "@testing-library/react-native";
-import { GAME_STATES, PLAYER_O, PLAYER_X } from "../../src/constants/common";
-
-const onPressCallbackGlobal = jest.fn();
-
-const mockProps = {
- board: board,
- gameState: GAME_STATES.inProgress,
- disabled: false,
- onPressUser: onPressCallbackGlobal,
-};
-
-describe("", () => {
- const BoardComponent = () => ;
-
- test("renders correctly", () => {
- const tree = renderer.create().toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- test("return the correct row and col indices when first square pressed", async () => {
- render();
-
- const firstSquare = screen.getByTestId("00");
-
- expect(firstSquare).toBeDefined();
-
- await userEvent.press(firstSquare);
-
- //enure the correct row and column indices are correct
- expect(onPressCallbackGlobal).toHaveBeenCalledWith(0, 0);
- });
-
- test("prevent pressing when board is disabled", async () => {
- const onPressCallback = jest.fn();
- render(
-
- );
-
- const firstSquare = screen.getByTestId("00");
-
- expect(firstSquare).toBeDefined();
-
- await userEvent.press(firstSquare);
-
- //enure callBack did not fire
- expect(onPressCallback).not.toHaveBeenCalled();
- });
-
- test("should display X and O for PLAYER_X and PLAYER_O", async () => {
- const boardCopy = board.map((arr) => arr.slice());
- boardCopy[0][0] = PLAYER_X;
- boardCopy[0][1] = PLAYER_O;
- const onPressCallback = jest.fn();
-
- render(
-
- );
-
- //Player X should exist on the board
- expect(screen.getByText("X")).toBeDefined();
-
- //Player O should exist on the board
- expect(screen.getByText("O")).toBeDefined();
-
- const squareX = screen.getByTestId("00");
- const squareO = screen.getByTestId("01");
-
- expect(squareX).toBeDefined();
- expect(squareO).toBeDefined();
- });
-
- test("should disable board when game is ended", async () => {
- const onPressCallback = jest.fn();
- render(
-
- );
-
- const square = screen.getByTestId("00");
-
- await userEvent.press(square);
-
- //enure board is disabled
- expect(onPressCallback).not.toHaveBeenCalled();
- });
-});
+import React from "react";
+import renderer from "react-test-renderer";
+import Board from "../../src/components/Board";
+import board from "../__mocks__/boardData.mock";
+import { render, screen, userEvent } from "@testing-library/react-native";
+import { GAME_STATES, PLAYER_O, PLAYER_X } from "../../src/constants/common";
+
+const onPressCallbackGlobal = jest.fn();
+
+const mockProps = {
+ board: board,
+ gameState: GAME_STATES.inProgress,
+ disabled: false,
+ onPressUser: onPressCallbackGlobal,
+};
+
+describe("", () => {
+ const BoardComponent = () => ;
+
+ test("renders correctly", () => {
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ test("return the correct row and col indices when first square pressed", async () => {
+ render();
+
+ const firstSquare = screen.getByTestId("00");
+
+ expect(firstSquare).toBeDefined();
+
+ await userEvent.press(firstSquare);
+
+ //enure the correct row and column indices are correct
+ expect(onPressCallbackGlobal).toHaveBeenCalledWith(0, 0);
+ });
+
+ test("prevent pressing when board is disabled", async () => {
+ const onPressCallback = jest.fn();
+ render(
+ ,
+ );
+
+ const firstSquare = screen.getByTestId("00");
+
+ expect(firstSquare).toBeDefined();
+
+ await userEvent.press(firstSquare);
+
+ //enure callBack did not fire
+ expect(onPressCallback).not.toHaveBeenCalled();
+ });
+
+ test("should display X and O for PLAYER_X and PLAYER_O", async () => {
+ const boardCopy = board.map((arr) => arr.slice());
+ boardCopy[0][0] = PLAYER_X;
+ boardCopy[0][1] = PLAYER_O;
+ const onPressCallback = jest.fn();
+
+ render(
+ ,
+ );
+
+ //Player X should exist on the board
+ expect(screen.getByText("X")).toBeDefined();
+
+ //Player O should exist on the board
+ expect(screen.getByText("O")).toBeDefined();
+
+ const squareX = screen.getByTestId("00");
+ const squareO = screen.getByTestId("01");
+
+ expect(squareX).toBeDefined();
+ expect(squareO).toBeDefined();
+ });
+
+ test("should disable board when game is ended", async () => {
+ const onPressCallback = jest.fn();
+ render(
+ ,
+ );
+
+ const square = screen.getByTestId("00");
+
+ await userEvent.press(square);
+
+ //enure board is disabled
+ expect(onPressCallback).not.toHaveBeenCalled();
+ });
+});
diff --git a/__tests__/components/Game.test.tsx b/__tests__/components/Game.test.tsx
index 054362b..a613aea 100644
--- a/__tests__/components/Game.test.tsx
+++ b/__tests__/components/Game.test.tsx
@@ -1,191 +1,191 @@
-import renderer from "react-test-renderer";
-import Game from "../../src/components/Game";
-import { render, screen, userEvent } from "@testing-library/react-native";
-
-import * as utils from "../../src/utils/common";
-
-const mockGetRandomMove = jest.spyOn(utils, "getRandomMove");
-
-describe("", () => {
- test("renders correctly", () => {
- const tree = renderer.create().toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- test("should display the correct labels when game is started", async () => {
- render();
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- expect(startGameBtn).toBeDefined();
-
- await userEvent.press(startGameBtn);
-
- expect(screen.getByText("USER .vs COMPUTER")).toBeDefined();
- expect(screen.getByText("Game in progress")).toBeDefined();
- expect(screen.getByText("Player X's turn")).toBeDefined();
- });
-
- test("should player X (human) make the first move by default, and then player O (computer)", async () => {
- jest.useFakeTimers();
-
- render();
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- const square = screen.getByTestId("00");
-
- await userEvent.press(square);
-
- //Player X should exist on the board
- expect(screen.getByText("X")).toBeDefined();
-
- expect(screen.getByText(`Player O's turn`)).toBeDefined();
-
- jest.runAllTimers();
-
- //Player O should exist now after making the next move
- expect(screen.getByText("O")).toBeDefined();
- });
-
- test("should simulate a winning game for the human .vs computer", async () => {
- jest.useFakeTimers();
-
- render();
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- //we want human to win using these combinations
- const square1 = screen.getByTestId("00");
- const square2 = screen.getByTestId("01");
- const square3 = screen.getByTestId("02");
-
- // human's move
- await userEvent.press(square1);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([1, 0]);
-
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square2);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([2, 1]);
-
- jest.runAllTimers();
-
- // human's move
- // at this point, human should win
- await userEvent.press(square3);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([1, 2]);
-
- jest.runAllTimers();
-
- expect(screen.getByText("Game over")).toBeDefined();
- expect(screen.getByText("The winner is player X")).toBeDefined();
- });
-
- test("should simulate a tie game between human and computer", async () => {
- jest.useFakeTimers();
-
- render();
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- //combinations for player X
- const square1 = screen.getByTestId("01");
- const square2 = screen.getByTestId("02");
- const square3 = screen.getByTestId("10");
- const square4 = screen.getByTestId("21");
- const square5 = screen.getByTestId("22");
-
- // human's move
- await userEvent.press(square1);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([0, 0]);
-
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square2);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([1, 2]);
-
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square3);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([1, 1]);
-
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square4);
-
- // computer's move
- mockGetRandomMove.mockReturnValue([2, 0]);
-
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square5);
-
- expect(screen.getByText("Game over")).toBeDefined();
- expect(screen.getByText("It's a tie")).toBeDefined();
- });
-
- test("should simulate a battle between AI and human", async () => {
- jest.useFakeTimers();
-
- render();
-
- const aiCheckbox = screen.getByTestId("aiCheckboxTestId");
-
- await userEvent.press(aiCheckbox);
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- //combinations for player X (human)
- const square1 = screen.getByTestId("00");
- const square2 = screen.getByTestId("02");
- const square3 = screen.getByTestId("10");
-
- // human's move
- await userEvent.press(square1);
-
- // AT's move
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square2);
-
- // AI's move
- jest.runAllTimers();
-
- // human's move
- await userEvent.press(square3);
-
- // AI's move
- jest.runAllTimers();
-
- // AI should win since the human's combinations above are easy to beat
- expect(screen.getByText("Game over")).toBeDefined();
- expect(screen.getByText("The winner is player O")).toBeDefined();
- });
-});
+import renderer from "react-test-renderer";
+import Game from "../../src/components/Game";
+import { render, screen, userEvent } from "@testing-library/react-native";
+
+import * as utils from "../../src/utils/common";
+
+const mockGetRandomMove = jest.spyOn(utils, "getRandomMove");
+
+describe("", () => {
+ test("renders correctly", () => {
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ test("should display the correct labels when game is started", async () => {
+ render();
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ expect(startGameBtn).toBeDefined();
+
+ await userEvent.press(startGameBtn);
+
+ expect(screen.getByText("USER .vs COMPUTER")).toBeDefined();
+ expect(screen.getByText("Game in progress")).toBeDefined();
+ expect(screen.getByText("Player X's turn")).toBeDefined();
+ });
+
+ test("should player X (human) make the first move by default, and then player O (computer)", async () => {
+ jest.useFakeTimers();
+
+ render();
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ const square = screen.getByTestId("00");
+
+ await userEvent.press(square);
+
+ //Player X should exist on the board
+ expect(screen.getByText("X")).toBeDefined();
+
+ expect(screen.getByText(`Player O's turn`)).toBeDefined();
+
+ jest.runAllTimers();
+
+ //Player O should exist now after making the next move
+ expect(screen.getByText("O")).toBeDefined();
+ });
+
+ test("should simulate a winning game for the human .vs computer", async () => {
+ jest.useFakeTimers();
+
+ render();
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ //we want human to win using these combinations
+ const square1 = screen.getByTestId("00");
+ const square2 = screen.getByTestId("01");
+ const square3 = screen.getByTestId("02");
+
+ // human's move
+ await userEvent.press(square1);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([1, 0]);
+
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square2);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([2, 1]);
+
+ jest.runAllTimers();
+
+ // human's move
+ // at this point, human should win
+ await userEvent.press(square3);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([1, 2]);
+
+ jest.runAllTimers();
+
+ expect(screen.getByText("Game over")).toBeDefined();
+ expect(screen.getByText("The winner is player X")).toBeDefined();
+ });
+
+ test("should simulate a tie game between human and computer", async () => {
+ jest.useFakeTimers();
+
+ render();
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ //combinations for player X
+ const square1 = screen.getByTestId("01");
+ const square2 = screen.getByTestId("02");
+ const square3 = screen.getByTestId("10");
+ const square4 = screen.getByTestId("21");
+ const square5 = screen.getByTestId("22");
+
+ // human's move
+ await userEvent.press(square1);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([0, 0]);
+
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square2);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([1, 2]);
+
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square3);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([1, 1]);
+
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square4);
+
+ // computer's move
+ mockGetRandomMove.mockReturnValue([2, 0]);
+
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square5);
+
+ expect(screen.getByText("Game over")).toBeDefined();
+ expect(screen.getByText("It's a tie")).toBeDefined();
+ });
+
+ test("should simulate a battle between AI and human", async () => {
+ jest.useFakeTimers();
+
+ render();
+
+ const aiCheckbox = screen.getByTestId("aiCheckboxTestId");
+
+ await userEvent.press(aiCheckbox);
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ //combinations for player X (human)
+ const square1 = screen.getByTestId("00");
+ const square2 = screen.getByTestId("02");
+ const square3 = screen.getByTestId("10");
+
+ // human's move
+ await userEvent.press(square1);
+
+ // AT's move
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square2);
+
+ // AI's move
+ jest.runAllTimers();
+
+ // human's move
+ await userEvent.press(square3);
+
+ // AI's move
+ jest.runAllTimers();
+
+ // AI should win since the human's combinations above are easy to beat
+ expect(screen.getByText("Game over")).toBeDefined();
+ expect(screen.getByText("The winner is player O")).toBeDefined();
+ });
+});
diff --git a/__tests__/components/Settings.test.tsx b/__tests__/components/Settings.test.tsx
index d0151de..e6ca4fa 100644
--- a/__tests__/components/Settings.test.tsx
+++ b/__tests__/components/Settings.test.tsx
@@ -1,95 +1,95 @@
-import React from "react";
-import renderer from "react-test-renderer";
-import { render, screen, userEvent } from "@testing-library/react-native";
-import { BOARD_SIZES } from "../../src/constants/common";
-import Settings from "../../src/components/Settings";
-
-const settingsMock = {
- playerX: "user",
- playerO: "computer",
- useAi: false,
- boardSize: BOARD_SIZES._3x3,
-};
-
-describe("", () => {
- test("renders correctly", () => {
- const onStartGameMock = jest.fn();
- const tree = renderer
- .create()
- .toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- test("should emit the correct settings", async () => {
- const onStartGameMock = jest.fn();
- render();
-
- const board3x3 = screen.getByTestId("3x3TestId");
- const board4x4 = screen.getByTestId("4x4TestId");
- const board5x5 = screen.getByTestId("5x5TestId");
-
- expect(board3x3).toBeDefined();
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- //default settings
- expect(onStartGameMock).toHaveBeenCalledWith(settingsMock);
-
- //select board 4x4
- await userEvent.press(board4x4);
- await userEvent.press(startGameBtn);
-
- expect(onStartGameMock).toHaveBeenCalledWith({
- ...settingsMock,
- boardSize: BOARD_SIZES._4x4,
- });
-
- //select board 5x5
- await userEvent.press(board5x5);
- await userEvent.press(startGameBtn);
-
- expect(onStartGameMock).toHaveBeenCalledWith({
- ...settingsMock,
- boardSize: BOARD_SIZES._5x5,
- });
- });
-
- test("should check the AI checkbox", async () => {
- const onStartGameMock = jest.fn();
- render();
-
- const aiCheckbox = screen.getByTestId("aiCheckboxTestId");
-
- await userEvent.press(aiCheckbox);
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- expect(onStartGameMock).toHaveBeenCalledWith({
- ...settingsMock,
- useAi: true,
- });
- });
-
- //FIXME: needs fixed!
- xtest("should select the right player from dropdown", async () => {
- const onStartGameMock = jest.fn();
- render();
-
- const computerPlayer = screen.getByText("User");
-
- await userEvent.press(computerPlayer);
-
- const startGameBtn = screen.getByTestId("startGameBtnTestId");
-
- await userEvent.press(startGameBtn);
-
- expect(onStartGameMock).toHaveBeenCalledWith({
- ...settingsMock,
- playerX: "computer",
- });
- });
-});
+import React from "react";
+import renderer from "react-test-renderer";
+import { render, screen, userEvent } from "@testing-library/react-native";
+import { BOARD_SIZES } from "../../src/constants/common";
+import Settings from "../../src/components/Settings";
+
+const settingsMock = {
+ playerX: "user",
+ playerO: "computer",
+ useAi: false,
+ boardSize: BOARD_SIZES._3x3,
+};
+
+describe("", () => {
+ test("renders correctly", () => {
+ const onStartGameMock = jest.fn();
+ const tree = renderer
+ .create()
+ .toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ test("should emit the correct settings", async () => {
+ const onStartGameMock = jest.fn();
+ render();
+
+ const board3x3 = screen.getByTestId("3x3TestId");
+ const board4x4 = screen.getByTestId("4x4TestId");
+ const board5x5 = screen.getByTestId("5x5TestId");
+
+ expect(board3x3).toBeDefined();
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ //default settings
+ expect(onStartGameMock).toHaveBeenCalledWith(settingsMock);
+
+ //select board 4x4
+ await userEvent.press(board4x4);
+ await userEvent.press(startGameBtn);
+
+ expect(onStartGameMock).toHaveBeenCalledWith({
+ ...settingsMock,
+ boardSize: BOARD_SIZES._4x4,
+ });
+
+ //select board 5x5
+ await userEvent.press(board5x5);
+ await userEvent.press(startGameBtn);
+
+ expect(onStartGameMock).toHaveBeenCalledWith({
+ ...settingsMock,
+ boardSize: BOARD_SIZES._5x5,
+ });
+ });
+
+ test("should check the AI checkbox", async () => {
+ const onStartGameMock = jest.fn();
+ render();
+
+ const aiCheckbox = screen.getByTestId("aiCheckboxTestId");
+
+ await userEvent.press(aiCheckbox);
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ expect(onStartGameMock).toHaveBeenCalledWith({
+ ...settingsMock,
+ useAi: true,
+ });
+ });
+
+ //FIXME: needs fixed!
+ xtest("should select the right player from dropdown", async () => {
+ const onStartGameMock = jest.fn();
+ render();
+
+ const computerPlayer = screen.getByText("User");
+
+ await userEvent.press(computerPlayer);
+
+ const startGameBtn = screen.getByTestId("startGameBtnTestId");
+
+ await userEvent.press(startGameBtn);
+
+ expect(onStartGameMock).toHaveBeenCalledWith({
+ ...settingsMock,
+ playerX: "computer",
+ });
+ });
+});
diff --git a/__tests__/components/Square.test.tsx b/__tests__/components/Square.test.tsx
index e323b0e..1aa78d3 100644
--- a/__tests__/components/Square.test.tsx
+++ b/__tests__/components/Square.test.tsx
@@ -1,48 +1,48 @@
-import React from "react";
-import renderer from "react-test-renderer";
-import { render, screen, userEvent } from "@testing-library/react-native";
-import Square from "../../src/components/Square";
-
-const onSquarePressMock = jest.fn();
-const propsMock = {
- value: "X",
- disabled: false,
- testID: "squareTestId",
- onSquarePress: onSquarePressMock,
-};
-
-describe("", () => {
- test("renders correctly", () => {
- const tree = renderer.create().toJSON();
- expect(tree).toMatchSnapshot();
- });
-
- test("should display the correct value, e.g. X", async () => {
- render();
-
- const x = screen.getByText("X");
-
- expect(x).toBeDefined();
- });
-
- test("should fire onSquarePress when square is pressed", async () => {
- render();
-
- const x = screen.getByText("X");
-
- await userEvent.press(x);
-
- expect(onSquarePressMock).toHaveBeenCalled();
- });
-
- test("should fire not onSquarePress when square is disabled", async () => {
- const callBack = jest.fn();
- render();
-
- const x = screen.getByText("X");
-
- await userEvent.press(x);
-
- expect(callBack).not.toHaveBeenCalled();
- });
-});
+import React from "react";
+import renderer from "react-test-renderer";
+import { render, screen, userEvent } from "@testing-library/react-native";
+import Square from "../../src/components/Square";
+
+const onSquarePressMock = jest.fn();
+const propsMock = {
+ value: "X",
+ disabled: false,
+ testID: "squareTestId",
+ onSquarePress: onSquarePressMock,
+};
+
+describe("", () => {
+ test("renders correctly", () => {
+ const tree = renderer.create().toJSON();
+ expect(tree).toMatchSnapshot();
+ });
+
+ test("should display the correct value, e.g. X", async () => {
+ render();
+
+ const x = screen.getByText("X");
+
+ expect(x).toBeDefined();
+ });
+
+ test("should fire onSquarePress when square is pressed", async () => {
+ render();
+
+ const x = screen.getByText("X");
+
+ await userEvent.press(x);
+
+ expect(onSquarePressMock).toHaveBeenCalled();
+ });
+
+ test("should fire not onSquarePress when square is disabled", async () => {
+ const callBack = jest.fn();
+ render();
+
+ const x = screen.getByText("X");
+
+ await userEvent.press(x);
+
+ expect(callBack).not.toHaveBeenCalled();
+ });
+});
diff --git a/babel.config.js b/babel.config.js
index 2900afe..73ebf58 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,6 +1,6 @@
-module.exports = function(api) {
+module.exports = function (api) {
api.cache(true);
return {
- presets: ['babel-preset-expo'],
+ presets: ["babel-preset-expo"],
};
};
diff --git a/package.json b/package.json
index b282f41..149499a 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,8 @@
"test": "jest --watch --coverage=false",
"testDebug": "jest -o --watch --coverage=false",
"testFinal": "jest",
- "updateSnapshots": "jest -u --coverage=false"
+ "updateSnapshots": "jest -u --coverage=false",
+ "lint": "eslint ."
},
"jest": {
"preset": "jest-expo",
@@ -36,8 +37,13 @@
"@types/jest": "^29.5.12",
"@types/react": "~18.2.45",
"@types/react-test-renderer": "^18.3.0",
+ "eslint": "^8.57.0",
+ "eslint-config-expo": "^7.0.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-prettier": "^5.2.1",
"jest": "^29.7.0",
"jest-expo": "^51.0.2",
+ "prettier": "^3.3.3",
"react-test-renderer": "18.2.0",
"typescript": "^5.1.3"
},
diff --git a/src/components/Board.tsx b/src/components/Board.tsx
index 546ef88..41aed82 100644
--- a/src/components/Board.tsx
+++ b/src/components/Board.tsx
@@ -1,50 +1,50 @@
-import { View, StyleSheet } from "react-native";
-import Square from "./Square";
-import { GameBoard } from "../types/types";
-import { GAME_STATES, PLAYER_X } from "../constants/common";
-
-interface Props {
- board: GameBoard;
- gameState: string;
- disabled?: boolean;
- onPressUser: (rowIndex: number, colIndex: number) => void;
-}
-
-/**
- * This is a presentational (stateless) component for the game board
- *
- * @param param0
- * @returns
- */
-function Board({ board, gameState, disabled, onPressUser }: Props) {
- return (
-
- {board.map((row, rowIndex) => (
-
- {row.map((_, colIndex) => {
- const value = board[rowIndex][colIndex];
-
- return (
- onPressUser(rowIndex, colIndex)}
- value={value ? (value === PLAYER_X ? "X" : "O") : null}
- />
- );
- })}
-
- ))}
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flexDirection: "row",
- paddingVertical: 10,
- },
-});
-
-export default Board;
+import { View, StyleSheet } from "react-native";
+import Square from "./Square";
+import { GameBoard } from "../types/types";
+import { GAME_STATES, PLAYER_X } from "../constants/common";
+
+interface Props {
+ board: GameBoard;
+ gameState: string;
+ disabled?: boolean;
+ onPressUser: (rowIndex: number, colIndex: number) => void;
+}
+
+/**
+ * This is a presentational (stateless) component for the game board
+ *
+ * @param param0
+ * @returns
+ */
+function Board({ board, gameState, disabled, onPressUser }: Props) {
+ return (
+
+ {board.map((row, rowIndex) => (
+
+ {row.map((_, colIndex) => {
+ const value = board[rowIndex][colIndex];
+
+ return (
+ onPressUser(rowIndex, colIndex)}
+ value={value ? (value === PLAYER_X ? "X" : "O") : null}
+ />
+ );
+ })}
+
+ ))}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flexDirection: "row",
+ paddingVertical: 10,
+ },
+});
+
+export default Board;
diff --git a/src/components/Game.tsx b/src/components/Game.tsx
index 6561039..25bf05d 100644
--- a/src/components/Game.tsx
+++ b/src/components/Game.tsx
@@ -1,253 +1,253 @@
-import { View, StyleSheet, Text, TouchableOpacity } from "react-native";
-import { useCallback, useEffect, useState } from "react";
-import Board from "./Board";
-import { BOARD_SIZES, GAME_STATES, PLAYER_X } from "../constants/common";
-import {
- createBoard,
- getComputerValue,
- getRandomMove,
- getUserValue,
- getWinner,
- getWinnerText,
- isComputerVsComputer,
- isUserVsUser,
- switchPlayer,
-} from "../utils/common";
-import { displayAlert } from "../utils/alert";
-import { GameBoard, GameSettings, Players } from "../types/types";
-import Settings from "./Settings";
-import { Colors } from "../constants/colors";
-import { getAiMove } from "../utils/ai";
-
-// create a 3x3 as the default board
-const emptyBoard = createBoard(BOARD_SIZES._3x3);
-
-/**
- *
- * Core component for handling game logic and implementation
- */
-function Game() {
- const [board, setBoard] = useState(emptyBoard);
- const [gameState, setGameState] = useState(GAME_STATES.notStarted);
- const [nextMove, setNextMove] = useState(null);
- const [winner, setWinner] = useState(null);
- const [players, setPlayers] = useState({
- user: null,
- computer: null,
- });
- const [currentPlayer, setCurrentPlayer] = useState(null);
- const [gameSettings, setGameSettings] = useState(null);
-
- // This function handles updating the board, invoked by userMove or computerMove function
- const updateBoard = useCallback(
- (row: number, col: number, player: number | null) => {
- player &&
- gameState === GAME_STATES.inProgress &&
- setBoard((board) => {
- const boardCopy = board.map((arr) => arr.slice());
- boardCopy[row][col] = player;
- return boardCopy;
- });
- },
- [gameState]
- );
-
- //This function handles the user's interaction on the board
- const userMove = (row: number, col: number) => {
- if (!board[row][col] && nextMove === players.user) {
- const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove;
-
- //if it's user vs. user, let the opposite of player X or O update the board
- updateBoard(row, col, players.computer ? players.user : currPlayer);
-
- //if it's user vs. user, let the user make the next move
- setNextMove(players.computer ? players.computer : players.user);
- setCurrentPlayer(currPlayer);
- }
- };
-
- //This function handles the computer's interaction on the board
- const computerMove = useCallback(() => {
- const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove;
-
- // if AI is incorporated, call getAiMove, otherwise call getRandomMove
- const [row, col] = gameSettings?.useAi
- ? getAiMove(board, gameSettings, currPlayer)
- : getRandomMove(board, gameSettings?.boardSize);
-
- //if it's computer vs. computer, let the opposite of player X or O update the board
- updateBoard(row, col, players.user ? players.computer : currPlayer);
-
- //if it's computer vs. computer, let the computer make the next move
- setNextMove(players.user ? players.user : players.computer);
- setCurrentPlayer(currPlayer);
- }, [board, players, currentPlayer, updateBoard]);
-
- // handler for when game is started
- const handleStartGame = (settings: GameSettings) => {
- setGameSettings(settings);
-
- const userVsUser = isUserVsUser(settings);
- const computerVsComputer = isComputerVsComputer(settings);
-
- const user = getUserValue(settings);
- const computer = getComputerValue(settings);
-
- setBoard(createBoard(settings.boardSize ?? BOARD_SIZES._3x3));
- setGameState(GAME_STATES.inProgress);
- setPlayers({
- user: computerVsComputer ? null : user,
- computer: userVsUser ? null : computer,
- });
-
- //Player X always makes the first move
- setNextMove(PLAYER_X);
- setCurrentPlayer(null);
- };
-
- //Reset all states when game is started
- const startNewGame = () => {
- setGameState(GAME_STATES.notStarted);
- setBoard(emptyBoard);
- setWinner(null);
- setNextMove(null);
- setCurrentPlayer(null);
- setGameSettings(null);
- };
-
- //We need this to keep track of whose next move is, and also ensure the right track of turns
- useEffect(() => {
- let timeout: any;
- const isComputerMove =
- nextMove &&
- nextMove === players.computer &&
- gameState !== GAME_STATES.ended;
-
- if (isComputerMove) {
- timeout = setTimeout(
- () => {
- computerMove();
- },
- // Give it more time when it's a computer .vs computer
- players.user ? 500 : 1200
- );
- }
- return () => {
- // clean up timeout
- if (timeout) {
- clearTimeout(timeout);
- }
- };
- }, [gameState, nextMove, players.computer, computerMove]);
-
- useEffect(() => {
- if (gameState !== GAME_STATES.ended) {
- const winnerValue = getWinner(board, gameSettings?.boardSize);
-
- if (winnerValue !== null) {
- //Return either a win or tie
- const winnerText = getWinnerText(winnerValue);
-
- setGameState(GAME_STATES.ended);
- setWinner(winnerText);
- setNextMove(null);
- displayAlert("Game over", winnerText);
- }
- }
- }, [board, nextMove, gameState]);
-
- const getPlayersTurnInfo = () => {
- let playerStateInfo = "";
- const playerVsItself = !players.user || !players.computer;
-
- if (playerVsItself) {
- playerStateInfo = winner
- ? winner
- : currentPlayer === PLAYER_X
- ? "Player O's turn"
- : "Player X's turn";
- } else {
- playerStateInfo = winner
- ? winner
- : nextMove === PLAYER_X
- ? "Player X's turn"
- : "Player O's turn";
- }
-
- return playerStateInfo;
- };
-
- const gameStateInfo =
- gameState === GAME_STATES.inProgress ? "Game in progress" : "Game over";
-
- return gameState === GAME_STATES.notStarted ? (
-
- Tic Tac Toe
- handleStartGame(settings)} />
-
- ) : (
-
-
- {`${gameSettings?.playerX?.toUpperCase()} .vs ${gameSettings?.playerO?.toUpperCase()}`}
- {gameStateInfo}
-
- userMove(rowIndex, colIndex)}
- />
-
- {getPlayersTurnInfo()}
-
- startNewGame()}>
- Start over
-
-
- );
-}
-
-const styles = StyleSheet.create({
- boardContainer: {},
- headerContainer: {
- alignItems: "center",
- paddingHorizontal: 10,
- },
- title: {
- fontSize: 38,
- padding: 10,
- fontWeight: "bold",
- },
- resetBtn: {
- padding: 10,
- marginTop: 24,
- borderColor: Colors.gray,
- borderWidth: 1,
- borderRadius: 5,
- alignItems: "center",
- },
- resetText: {
- fontSize: 20,
- },
- gameStateText: {
- fontSize: 18,
- },
- gameStateTextContainer: {
- alignItems: "center",
- rowGap: 20,
- },
- playerText: {
- fontSize: 16,
- },
- playersInfoText: {
- fontSize: 18,
- fontWeight: "bold",
- },
- playerTextContainer: {
- alignItems: "center",
- },
-});
-
-export default Game;
+import { View, StyleSheet, Text, TouchableOpacity } from "react-native";
+import { useCallback, useEffect, useState } from "react";
+import Board from "./Board";
+import { BOARD_SIZES, GAME_STATES, PLAYER_X } from "../constants/common";
+import {
+ createBoard,
+ getComputerValue,
+ getRandomMove,
+ getUserValue,
+ getWinner,
+ getWinnerText,
+ isComputerVsComputer,
+ isUserVsUser,
+ switchPlayer,
+} from "../utils/common";
+import { displayAlert } from "../utils/alert";
+import { GameBoard, GameSettings, Players } from "../types/types";
+import Settings from "./Settings";
+import { Colors } from "../constants/colors";
+import { getAiMove } from "../utils/ai";
+
+// create a 3x3 as the default board
+const emptyBoard = createBoard(BOARD_SIZES._3x3);
+
+/**
+ *
+ * Core component for handling game logic and implementation
+ */
+function Game() {
+ const [board, setBoard] = useState(emptyBoard);
+ const [gameState, setGameState] = useState(GAME_STATES.notStarted);
+ const [nextMove, setNextMove] = useState(null);
+ const [winner, setWinner] = useState(null);
+ const [players, setPlayers] = useState({
+ user: null,
+ computer: null,
+ });
+ const [currentPlayer, setCurrentPlayer] = useState(null);
+ const [gameSettings, setGameSettings] = useState(null);
+
+ // This function handles updating the board, invoked by userMove or computerMove function
+ const updateBoard = useCallback(
+ (row: number, col: number, player: number | null) => {
+ player &&
+ gameState === GAME_STATES.inProgress &&
+ setBoard((board) => {
+ const boardCopy = board.map((arr) => arr.slice());
+ boardCopy[row][col] = player;
+ return boardCopy;
+ });
+ },
+ [gameState],
+ );
+
+ //This function handles the user's interaction on the board
+ const userMove = (row: number, col: number) => {
+ if (!board[row][col] && nextMove === players.user) {
+ const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove;
+
+ //if it's user vs. user, let the opposite of player X or O update the board
+ updateBoard(row, col, players.computer ? players.user : currPlayer);
+
+ //if it's user vs. user, let the user make the next move
+ setNextMove(players.computer ? players.computer : players.user);
+ setCurrentPlayer(currPlayer);
+ }
+ };
+
+ //This function handles the computer's interaction on the board
+ const computerMove = useCallback(() => {
+ const currPlayer = currentPlayer ? switchPlayer(currentPlayer) : nextMove;
+
+ // if AI is incorporated, call getAiMove, otherwise call getRandomMove
+ const [row, col] = gameSettings?.useAi
+ ? getAiMove(board, gameSettings, currPlayer)
+ : getRandomMove(board, gameSettings?.boardSize);
+
+ //if it's computer vs. computer, let the opposite of player X or O update the board
+ updateBoard(row, col, players.user ? players.computer : currPlayer);
+
+ //if it's computer vs. computer, let the computer make the next move
+ setNextMove(players.user ? players.user : players.computer);
+ setCurrentPlayer(currPlayer);
+ }, [board, players, currentPlayer, updateBoard]);
+
+ // handler for when game is started
+ const handleStartGame = (settings: GameSettings) => {
+ setGameSettings(settings);
+
+ const userVsUser = isUserVsUser(settings);
+ const computerVsComputer = isComputerVsComputer(settings);
+
+ const user = getUserValue(settings);
+ const computer = getComputerValue(settings);
+
+ setBoard(createBoard(settings.boardSize ?? BOARD_SIZES._3x3));
+ setGameState(GAME_STATES.inProgress);
+ setPlayers({
+ user: computerVsComputer ? null : user,
+ computer: userVsUser ? null : computer,
+ });
+
+ //Player X always makes the first move
+ setNextMove(PLAYER_X);
+ setCurrentPlayer(null);
+ };
+
+ //Reset all states when game is started
+ const startNewGame = () => {
+ setGameState(GAME_STATES.notStarted);
+ setBoard(emptyBoard);
+ setWinner(null);
+ setNextMove(null);
+ setCurrentPlayer(null);
+ setGameSettings(null);
+ };
+
+ //We need this to keep track of whose next move is, and also ensure the right track of turns
+ useEffect(() => {
+ let timeout: any;
+ const isComputerMove =
+ nextMove &&
+ nextMove === players.computer &&
+ gameState !== GAME_STATES.ended;
+
+ if (isComputerMove) {
+ timeout = setTimeout(
+ () => {
+ computerMove();
+ },
+ // Give it more time when it's a computer .vs computer
+ players.user ? 500 : 1200,
+ );
+ }
+ return () => {
+ // clean up timeout
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+ };
+ }, [gameState, nextMove, players.computer, computerMove]);
+
+ useEffect(() => {
+ if (gameState !== GAME_STATES.ended) {
+ const winnerValue = getWinner(board, gameSettings?.boardSize);
+
+ if (winnerValue !== null) {
+ //Return either a win or tie
+ const winnerText = getWinnerText(winnerValue);
+
+ setGameState(GAME_STATES.ended);
+ setWinner(winnerText);
+ setNextMove(null);
+ displayAlert("Game over", winnerText);
+ }
+ }
+ }, [board, nextMove, gameState]);
+
+ const getPlayersTurnInfo = () => {
+ let playerStateInfo = "";
+ const playerVsItself = !players.user || !players.computer;
+
+ if (playerVsItself) {
+ playerStateInfo = winner
+ ? winner
+ : currentPlayer === PLAYER_X
+ ? "Player O's turn"
+ : "Player X's turn";
+ } else {
+ playerStateInfo = winner
+ ? winner
+ : nextMove === PLAYER_X
+ ? "Player X's turn"
+ : "Player O's turn";
+ }
+
+ return playerStateInfo;
+ };
+
+ const gameStateInfo =
+ gameState === GAME_STATES.inProgress ? "Game in progress" : "Game over";
+
+ return gameState === GAME_STATES.notStarted ? (
+
+ Tic Tac Toe
+ handleStartGame(settings)} />
+
+ ) : (
+
+
+ {`${gameSettings?.playerX?.toUpperCase()} .vs ${gameSettings?.playerO?.toUpperCase()}`}
+ {gameStateInfo}
+
+ userMove(rowIndex, colIndex)}
+ />
+
+ {getPlayersTurnInfo()}
+
+ startNewGame()}>
+ Start over
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ boardContainer: {},
+ headerContainer: {
+ alignItems: "center",
+ paddingHorizontal: 10,
+ },
+ title: {
+ fontSize: 38,
+ padding: 10,
+ fontWeight: "bold",
+ },
+ resetBtn: {
+ padding: 10,
+ marginTop: 24,
+ borderColor: Colors.gray,
+ borderWidth: 1,
+ borderRadius: 5,
+ alignItems: "center",
+ },
+ resetText: {
+ fontSize: 20,
+ },
+ gameStateText: {
+ fontSize: 18,
+ },
+ gameStateTextContainer: {
+ alignItems: "center",
+ rowGap: 20,
+ },
+ playerText: {
+ fontSize: 16,
+ },
+ playersInfoText: {
+ fontSize: 18,
+ fontWeight: "bold",
+ },
+ playerTextContainer: {
+ alignItems: "center",
+ },
+});
+
+export default Game;
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx
index 98d15a7..3bfdeb0 100644
--- a/src/components/Settings.tsx
+++ b/src/components/Settings.tsx
@@ -1,266 +1,266 @@
-import {
- View,
- StyleSheet,
- TouchableOpacity,
- Text,
- Platform,
-} from "react-native";
-import { BOARD_SIZES, PLAYER_TYPES } from "../constants/common";
-import React, { useEffect, useState } from "react";
-import { Picker } from "@react-native-picker/picker";
-import { GameSettings, PlayerTypes } from "../types/types";
-import RadioButton from "./ui/RadioButton";
-import { Colors } from "../constants/colors";
-import Checkbox from "expo-checkbox";
-
-interface Props {
- onStartGame: (settings: GameSettings) => void;
-}
-
-type GameSettingsKeys = keyof GameSettings;
-
-function Settings({ onStartGame }: Props) {
- const [settings, setSettings] = useState({
- playerX: "user",
- playerO: "computer",
- useAi: false,
- boardSize: BOARD_SIZES._3x3,
- });
- const [isUseAiChecked, setIsUseAiChecked] = useState(false);
-
- const handleBoardSizePress = (value: number) => {
- setSettings((currentSettings) => ({
- ...currentSettings,
- boardSize: value,
- }));
- };
-
- const handleAiCheckboxPress = (value: boolean) => {
- setIsUseAiChecked(value);
- setSettings((currentSettings) => ({
- ...currentSettings,
- useAi: value,
- }));
- };
-
- const handlePlayerDropdownPress = (
- value: PlayerTypes | null,
- key: GameSettingsKeys
- ) => {
- setSettings((currentSettings) => ({
- ...currentSettings,
- [key]: value,
- }));
- };
-
- const disableStartGameBtn = !settings.playerX || !settings.playerO;
-
- const disableAiCheckbox =
- (settings.playerX !== PLAYER_TYPES.computer &&
- settings.playerO !== PLAYER_TYPES.computer) ||
- //TODO: Currently AI is only available for 3x3 board.
- //There seems to be a performance issue that might cause your device (or virtual device)
- //to freeze when using the AI Algorithm for other board sizes.
- settings.boardSize !== BOARD_SIZES._3x3;
-
- useEffect(() => {
- disableAiCheckbox && setIsUseAiChecked(false);
- }, [disableAiCheckbox]);
-
- return (
-
-
-
-
- handleBoardSizePress(BOARD_SIZES._3x3)}
- />
- 3x3
-
-
- handleBoardSizePress(BOARD_SIZES._4x4)}
- />
- 4x4
-
-
- handleBoardSizePress(BOARD_SIZES._5x5)}
- />
- 5x5
-
-
-
-
- Incorporate AI
-
-
- Select player "X" and "O" to start the game
-
-
-
-
- style={styles.picker}
- mode="dropdown"
- selectedValue={settings.playerX}
- onValueChange={(itemValue) =>
- handlePlayerDropdownPress(itemValue, "playerX")
- }
- >
- {Platform.OS === "android" && (
-
- )}
-
-
-
-
-
-
- style={styles.picker}
- mode="dropdown"
- selectedValue={settings.playerO}
- onValueChange={(itemValue) =>
- handlePlayerDropdownPress(itemValue, "playerO")
- }
- >
- {Platform.OS === "android" && (
-
- )}
-
-
-
-
-
-
-
- onStartGame(settings)}
- disabled={disableStartGameBtn}
- >
-
- Start game
-
-
-
-
- );
-}
-
-const getAndroidPickerStyles = () =>
- Platform.OS === "android" && {
- borderRadius: 10,
- borderWidth: 1,
- borderColor: Colors.gray,
- };
-
-const styles = StyleSheet.create({
- container: {
- rowGap: 32,
- },
- boardSizesContainer: {
- flexDirection: "row",
- gap: 20,
- alignItems: "center",
- },
- settingsContainer: {
- alignItems: "center",
- paddingHorizontal: 15,
- },
- selectPlayersText: {
- fontSize: 16,
- paddingBottom: Platform.OS === "ios" ? 70 : 10,
- paddingTop: 20,
- },
- selectorBtn: {
- alignItems: "center",
- backgroundColor: Colors.grayLight,
- padding: 10,
- margin: 10,
- width: "100%",
- borderRadius: 5,
- },
- startGameText: {
- fontSize: 18,
- },
- pickersContainer: {
- flexDirection: "row",
- gap: 10,
- },
- pickerContainer: {
- height: 40,
- flexDirection: "row",
- alignItems: "center",
- width: "50%",
- },
- picker: {
- width: "100%",
- },
- pickerItem: {
- color: "gray",
- },
- radioBtnContainer: {
- flexDirection: "row",
- justifyContent: "center",
- alignItems: "center",
- paddingVertical: 20,
- padding: 20,
- gap: 5,
- },
- radioBtnText: {
- fontSize: 18,
- },
- startGameBtnContainer: {
- justifyContent: "center",
- alignItems: "center",
- paddingHorizontal: 15,
- paddingTop: 8,
- },
- checkboxContainer: {
- marginHorizontal: 16,
- marginVertical: 20,
- flexDirection: "row",
- alignItems: "center",
- },
- section: {
- flexDirection: "row",
- alignItems: "center",
- },
- paragraph: {
- fontSize: 15,
- },
- checkbox: {
- margin: 8,
- },
- checkboxText: {
- fontSize: 15,
- },
-});
-
-export default Settings;
+import {
+ View,
+ StyleSheet,
+ TouchableOpacity,
+ Text,
+ Platform,
+} from "react-native";
+import { BOARD_SIZES, PLAYER_TYPES } from "../constants/common";
+import React, { useEffect, useState } from "react";
+import { Picker } from "@react-native-picker/picker";
+import { GameSettings, PlayerTypes } from "../types/types";
+import RadioButton from "./ui/RadioButton";
+import { Colors } from "../constants/colors";
+import Checkbox from "expo-checkbox";
+
+interface Props {
+ onStartGame: (settings: GameSettings) => void;
+}
+
+type GameSettingsKeys = keyof GameSettings;
+
+function Settings({ onStartGame }: Props) {
+ const [settings, setSettings] = useState({
+ playerX: "user",
+ playerO: "computer",
+ useAi: false,
+ boardSize: BOARD_SIZES._3x3,
+ });
+ const [isUseAiChecked, setIsUseAiChecked] = useState(false);
+
+ const handleBoardSizePress = (value: number) => {
+ setSettings((currentSettings) => ({
+ ...currentSettings,
+ boardSize: value,
+ }));
+ };
+
+ const handleAiCheckboxPress = (value: boolean) => {
+ setIsUseAiChecked(value);
+ setSettings((currentSettings) => ({
+ ...currentSettings,
+ useAi: value,
+ }));
+ };
+
+ const handlePlayerDropdownPress = (
+ value: PlayerTypes | null,
+ key: GameSettingsKeys,
+ ) => {
+ setSettings((currentSettings) => ({
+ ...currentSettings,
+ [key]: value,
+ }));
+ };
+
+ const disableStartGameBtn = !settings.playerX || !settings.playerO;
+
+ const disableAiCheckbox =
+ (settings.playerX !== PLAYER_TYPES.computer &&
+ settings.playerO !== PLAYER_TYPES.computer) ||
+ //TODO: Currently AI is only available for 3x3 board.
+ //There seems to be a performance issue that might cause your device (or virtual device)
+ //to freeze when using the AI Algorithm for other board sizes.
+ settings.boardSize !== BOARD_SIZES._3x3;
+
+ useEffect(() => {
+ disableAiCheckbox && setIsUseAiChecked(false);
+ }, [disableAiCheckbox]);
+
+ return (
+
+
+
+
+ handleBoardSizePress(BOARD_SIZES._3x3)}
+ />
+ 3x3
+
+
+ handleBoardSizePress(BOARD_SIZES._4x4)}
+ />
+ 4x4
+
+
+ handleBoardSizePress(BOARD_SIZES._5x5)}
+ />
+ 5x5
+
+
+
+
+ Incorporate AI
+
+
+ Select player "X" and "O" to start the game
+
+
+
+
+ style={styles.picker}
+ mode="dropdown"
+ selectedValue={settings.playerX}
+ onValueChange={(itemValue) =>
+ handlePlayerDropdownPress(itemValue, "playerX")
+ }
+ >
+ {Platform.OS === "android" && (
+
+ )}
+
+
+
+
+
+
+ style={styles.picker}
+ mode="dropdown"
+ selectedValue={settings.playerO}
+ onValueChange={(itemValue) =>
+ handlePlayerDropdownPress(itemValue, "playerO")
+ }
+ >
+ {Platform.OS === "android" && (
+
+ )}
+
+
+
+
+
+
+
+ onStartGame(settings)}
+ disabled={disableStartGameBtn}
+ >
+
+ Start game
+
+
+
+
+ );
+}
+
+const getAndroidPickerStyles = () =>
+ Platform.OS === "android" && {
+ borderRadius: 10,
+ borderWidth: 1,
+ borderColor: Colors.gray,
+ };
+
+const styles = StyleSheet.create({
+ container: {
+ rowGap: 32,
+ },
+ boardSizesContainer: {
+ flexDirection: "row",
+ gap: 20,
+ alignItems: "center",
+ },
+ settingsContainer: {
+ alignItems: "center",
+ paddingHorizontal: 15,
+ },
+ selectPlayersText: {
+ fontSize: 16,
+ paddingBottom: Platform.OS === "ios" ? 70 : 10,
+ paddingTop: 20,
+ },
+ selectorBtn: {
+ alignItems: "center",
+ backgroundColor: Colors.grayLight,
+ padding: 10,
+ margin: 10,
+ width: "100%",
+ borderRadius: 5,
+ },
+ startGameText: {
+ fontSize: 18,
+ },
+ pickersContainer: {
+ flexDirection: "row",
+ gap: 10,
+ },
+ pickerContainer: {
+ height: 40,
+ flexDirection: "row",
+ alignItems: "center",
+ width: "50%",
+ },
+ picker: {
+ width: "100%",
+ },
+ pickerItem: {
+ color: "gray",
+ },
+ radioBtnContainer: {
+ flexDirection: "row",
+ justifyContent: "center",
+ alignItems: "center",
+ paddingVertical: 20,
+ padding: 20,
+ gap: 5,
+ },
+ radioBtnText: {
+ fontSize: 18,
+ },
+ startGameBtnContainer: {
+ justifyContent: "center",
+ alignItems: "center",
+ paddingHorizontal: 15,
+ paddingTop: 8,
+ },
+ checkboxContainer: {
+ marginHorizontal: 16,
+ marginVertical: 20,
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ section: {
+ flexDirection: "row",
+ alignItems: "center",
+ },
+ paragraph: {
+ fontSize: 15,
+ },
+ checkbox: {
+ margin: 8,
+ },
+ checkboxText: {
+ fontSize: 15,
+ },
+});
+
+export default Settings;
diff --git a/src/components/Square.tsx b/src/components/Square.tsx
index 95845bb..4d5a81b 100644
--- a/src/components/Square.tsx
+++ b/src/components/Square.tsx
@@ -1,43 +1,43 @@
-import { StyleSheet, Text, Dimensions, TouchableHighlight } from "react-native";
-import { Colors } from "../constants/colors";
-
-const { width, height } = Dimensions.get("window");
-
-console.log(width, height);
-
-interface Props {
- value: string | null;
- disabled?: boolean;
- testID?: string;
- onSquarePress: () => void;
-}
-
-function Square({ value, disabled, testID, onSquarePress }: Props) {
- return (
-
- {value}
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- borderWidth: 1,
- flexDirection: "row",
- width: 76,
- height: 76,
- alignItems: "center",
- justifyContent: "center",
- },
- label: {
- fontSize: 40,
- },
-});
-
-export default Square;
+import { StyleSheet, Text, Dimensions, TouchableHighlight } from "react-native";
+import { Colors } from "../constants/colors";
+
+const { width, height } = Dimensions.get("window");
+
+console.log(width, height);
+
+interface Props {
+ value: string | null;
+ disabled?: boolean;
+ testID?: string;
+ onSquarePress: () => void;
+}
+
+function Square({ value, disabled, testID, onSquarePress }: Props) {
+ return (
+
+ {value}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ borderWidth: 1,
+ flexDirection: "row",
+ width: 76,
+ height: 76,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ label: {
+ fontSize: 40,
+ },
+});
+
+export default Square;
diff --git a/src/constants/common.ts b/src/constants/common.ts
index 1e5170a..d1f396b 100644
--- a/src/constants/common.ts
+++ b/src/constants/common.ts
@@ -1,20 +1,20 @@
-export const BOARD_SIZES = {
- _3x3: 3,
- _4x4: 4,
- _5x5: 5,
-};
-
-export const PLAYER_X = 1;
-export const PLAYER_O = 2;
-export const TIE = 0;
-
-export const PLAYER_TYPES = {
- user: "user",
- computer: "computer",
-};
-
-export const GAME_STATES = {
- notStarted: "not_started",
- inProgress: "in_progress",
- ended: "ended",
-};
+export const BOARD_SIZES = {
+ _3x3: 3,
+ _4x4: 4,
+ _5x5: 5,
+};
+
+export const PLAYER_X = 1;
+export const PLAYER_O = 2;
+export const TIE = 0;
+
+export const PLAYER_TYPES = {
+ user: "user",
+ computer: "computer",
+};
+
+export const GAME_STATES = {
+ notStarted: "not_started",
+ inProgress: "in_progress",
+ ended: "ended",
+};
diff --git a/src/types/types.ts b/src/types/types.ts
index 8bb31dc..fb5ad94 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -1,12 +1,12 @@
-export type PlayerTypes = "user" | "computer";
-export type GameBoard = Array[];
-export type GameSettings = {
- playerX: PlayerTypes | null;
- playerO: PlayerTypes | null;
- boardSize: number | null;
- useAi: boolean;
-};
-export type Players = {
- user: number | null;
- computer: number | null;
-};
+export type PlayerTypes = "user" | "computer";
+export type GameBoard = (number | null)[][];
+export type GameSettings = {
+ playerX: PlayerTypes | null;
+ playerO: PlayerTypes | null;
+ boardSize: number | null;
+ useAi: boolean;
+};
+export type Players = {
+ user: number | null;
+ computer: number | null;
+};
diff --git a/src/utils/ai.ts b/src/utils/ai.ts
index 23d2942..2428526 100644
--- a/src/utils/ai.ts
+++ b/src/utils/ai.ts
@@ -1,222 +1,222 @@
-import {
- BOARD_SIZES,
- PLAYER_O,
- PLAYER_TYPES,
- PLAYER_X,
-} from "../constants/common";
-import { GameBoard, GameSettings } from "../types/types";
-import { isComputerVsComputer, switchPlayer } from "./common";
-
-/**
- *
- * This file has functions for implementing AI using Minimax Algorithm
- *
- * Resource: https://www.geeksforgeeks.org/finding-optimal-move-in-tic-tac-toe-using-minimax-algorithm-in-game-theory/
- */
-
-/**
- * This function returns true if there are moves
- remaining on the board. It returns false if
- there are no moves left to play.
- * @param board
- * @param boardSize
- * @returns
- */
-function isMovesLeft(board: GameBoard, boardSize: number) {
- for (let i = 0; i < boardSize; i++)
- for (let j = 0; j < boardSize; j++) if (board[i][j] === null) return true;
-
- return false;
-}
-
-/**
- * This is the evaluation function as discussed
- * in the previous article ( http://goo.gl/sJgv68 )
- * @param b
- * @param human
- * @param ai
- * @param boardSize
- * @returns
- */
-function evaluate(b: GameBoard, human: number, ai: number, boardSize: number) {
- // Checking for Rows for X or O victory.
- for (let row = 0; row < boardSize; row++) {
- if (b[row][0] == b[row][1] && b[row][1] == b[row][2]) {
- if (b[row][0] == ai) return +10;
- else if (b[row][0] == human) return -10;
- }
- }
-
- // Checking for Columns for X or O victory.
- for (let col = 0; col < boardSize; col++) {
- if (b[0][col] == b[1][col] && b[1][col] == b[2][col]) {
- if (b[0][col] == ai) return +10;
- else if (b[0][col] == human) return -10;
- }
- }
-
- // Checking for Diagonals for X or O victory.
- if (b[0][0] == b[1][1] && b[1][1] == b[2][2]) {
- if (b[0][0] == ai) return +10;
- else if (b[0][0] == human) return -10;
- }
-
- if (b[0][2] == b[1][1] && b[1][1] == b[2][0]) {
- if (b[0][2] == ai) return +10;
- else if (b[0][2] == human) return -10;
- }
-
- // Else if none of them have
- // won then return 0
- return 0;
-}
-
-/**
- * This is the minimax function. It
- * considers all the possible ways
- the game can go and returns the
- value of the board
- * @param board
- * @param depth
- * @param isMax
- * @param human
- * @param ai
- * @param boardSize
- * @returns
- */
-function minimax(
- board: GameBoard,
- depth: number,
- isMax: boolean,
- human: number,
- ai: number,
- boardSize: number
-) {
- let score = evaluate(board, human, ai, boardSize);
-
- // If Maximizer has won the game
- // return his/her evaluated score
- if (score == 10) return score;
-
- // If Minimizer has won the game
- // return his/her evaluated score
- if (score == -10) return score;
-
- // If there are no more moves and
- // no winner then it is a tie
- if (isMovesLeft(board, boardSize) == false) return 0;
-
- // If this maximizer's move
- if (isMax) {
- let best = -1000;
-
- // Traverse all cells
- for (let i = 0; i < boardSize; i++) {
- for (let j = 0; j < boardSize; j++) {
- // Check if cell is empty
- if (board[i][j] === null) {
- // Make the move
- board[i][j] = ai;
-
- // Call minimax recursively
- // and choose the maximum value
- best = Math.max(
- best,
- minimax(board, depth + 1, !isMax, human, ai, boardSize)
- );
-
- // Undo the move
- board[i][j] = null;
- }
- }
- }
- return best;
- }
-
- // If this minimizer's move
- else {
- let best = 1000;
-
- // Traverse all cells
- for (let i = 0; i < boardSize; i++) {
- for (let j = 0; j < boardSize; j++) {
- // Check if cell is empty
- if (board[i][j] == null) {
- // Make the move
- board[i][j] = human;
-
- // Call minimax recursively and
- // choose the minimum value
- best = Math.min(
- best,
- minimax(board, depth + 1, !isMax, human, ai, boardSize)
- );
-
- // Undo the move
- board[i][j] = null;
- }
- }
- }
- return best;
- }
-}
-
-// This will return the best possible
-// move for the player
-export function getAiMove(
- board: GameBoard,
- settings: GameSettings | null,
- currentPlayer: number | null
-) {
- let human = PLAYER_X,
- ai = PLAYER_O;
-
- const computerVsComputer = settings ? isComputerVsComputer(settings) : false;
-
- if (computerVsComputer) {
- const currPlayer = currentPlayer ?? PLAYER_X;
- human = switchPlayer(currPlayer);
- ai = currPlayer;
- } else {
- human = settings?.playerX === PLAYER_TYPES.user ? PLAYER_X : PLAYER_O;
- ai = switchPlayer(human);
- }
-
- const boardSize = settings?.boardSize ?? BOARD_SIZES._3x3;
-
- let bestVal = -1000;
- let row = -1;
- let col = -1;
-
- // Traverse all cells, evaluate
- // minimax function for all empty
- // cells. And return the cell
- // with optimal value.
- for (let i = 0; i < boardSize; i++) {
- for (let j = 0; j < boardSize; j++) {
- // Check if cell is empty
- if (board[i][j] === null) {
- // Make the move
- board[i][j] = ai;
-
- // compute evaluation function
- // for this move.
- let moveVal = minimax(board, 0, false, human, ai, boardSize);
-
- // Undo the move
- board[i][j] = null;
-
- // If the value of the current move
- // is more than the best value, then
- // update best
- if (moveVal > bestVal) {
- row = i;
- col = j;
- bestVal = moveVal;
- }
- }
- }
- }
-
- return [row, col];
-}
+import {
+ BOARD_SIZES,
+ PLAYER_O,
+ PLAYER_TYPES,
+ PLAYER_X,
+} from "../constants/common";
+import { GameBoard, GameSettings } from "../types/types";
+import { isComputerVsComputer, switchPlayer } from "./common";
+
+/**
+ *
+ * This file has functions for implementing AI using Minimax Algorithm
+ *
+ * Resource: https://www.geeksforgeeks.org/finding-optimal-move-in-tic-tac-toe-using-minimax-algorithm-in-game-theory/
+ */
+
+/**
+ * This function returns true if there are moves
+ remaining on the board. It returns false if
+ there are no moves left to play.
+ * @param board
+ * @param boardSize
+ * @returns
+ */
+function isMovesLeft(board: GameBoard, boardSize: number) {
+ for (let i = 0; i < boardSize; i++)
+ for (let j = 0; j < boardSize; j++) if (board[i][j] === null) return true;
+
+ return false;
+}
+
+/**
+ * This is the evaluation function as discussed
+ * in the previous article ( http://goo.gl/sJgv68 )
+ * @param b
+ * @param human
+ * @param ai
+ * @param boardSize
+ * @returns
+ */
+function evaluate(b: GameBoard, human: number, ai: number, boardSize: number) {
+ // Checking for Rows for X or O victory.
+ for (let row = 0; row < boardSize; row++) {
+ if (b[row][0] === b[row][1] && b[row][1] === b[row][2]) {
+ if (b[row][0] === ai) return +10;
+ else if (b[row][0] === human) return -10;
+ }
+ }
+
+ // Checking for Columns for X or O victory.
+ for (let col = 0; col < boardSize; col++) {
+ if (b[0][col] === b[1][col] && b[1][col] === b[2][col]) {
+ if (b[0][col] === ai) return +10;
+ else if (b[0][col] === human) return -10;
+ }
+ }
+
+ // Checking for Diagonals for X or O victory.
+ if (b[0][0] === b[1][1] && b[1][1] === b[2][2]) {
+ if (b[0][0] === ai) return +10;
+ else if (b[0][0] === human) return -10;
+ }
+
+ if (b[0][2] === b[1][1] && b[1][1] === b[2][0]) {
+ if (b[0][2] === ai) return +10;
+ else if (b[0][2] === human) return -10;
+ }
+
+ // Else if none of them have
+ // won then return 0
+ return 0;
+}
+
+/**
+ * This is the minimax function. It
+ * considers all the possible ways
+ the game can go and returns the
+ value of the board
+ * @param board
+ * @param depth
+ * @param isMax
+ * @param human
+ * @param ai
+ * @param boardSize
+ * @returns
+ */
+function minimax(
+ board: GameBoard,
+ depth: number,
+ isMax: boolean,
+ human: number,
+ ai: number,
+ boardSize: number,
+) {
+ let score = evaluate(board, human, ai, boardSize);
+
+ // If Maximizer has won the game
+ // return his/her evaluated score
+ if (score === 10) return score;
+
+ // If Minimizer has won the game
+ // return his/her evaluated score
+ if (score === -10) return score;
+
+ // If there are no more moves and
+ // no winner then it is a tie
+ if (isMovesLeft(board, boardSize) === false) return 0;
+
+ // If this maximizer's move
+ if (isMax) {
+ let best = -1000;
+
+ // Traverse all cells
+ for (let i = 0; i < boardSize; i++) {
+ for (let j = 0; j < boardSize; j++) {
+ // Check if cell is empty
+ if (board[i][j] === null) {
+ // Make the move
+ board[i][j] = ai;
+
+ // Call minimax recursively
+ // and choose the maximum value
+ best = Math.max(
+ best,
+ minimax(board, depth + 1, !isMax, human, ai, boardSize),
+ );
+
+ // Undo the move
+ board[i][j] = null;
+ }
+ }
+ }
+ return best;
+ }
+
+ // If this minimizer's move
+ else {
+ let best = 1000;
+
+ // Traverse all cells
+ for (let i = 0; i < boardSize; i++) {
+ for (let j = 0; j < boardSize; j++) {
+ // Check if cell is empty
+ if (board[i][j] == null) {
+ // Make the move
+ board[i][j] = human;
+
+ // Call minimax recursively and
+ // choose the minimum value
+ best = Math.min(
+ best,
+ minimax(board, depth + 1, !isMax, human, ai, boardSize),
+ );
+
+ // Undo the move
+ board[i][j] = null;
+ }
+ }
+ }
+ return best;
+ }
+}
+
+// This will return the best possible
+// move for the player
+export function getAiMove(
+ board: GameBoard,
+ settings: GameSettings | null,
+ currentPlayer: number | null,
+) {
+ let human = PLAYER_X,
+ ai = PLAYER_O;
+
+ const computerVsComputer = settings ? isComputerVsComputer(settings) : false;
+
+ if (computerVsComputer) {
+ const currPlayer = currentPlayer ?? PLAYER_X;
+ human = switchPlayer(currPlayer);
+ ai = currPlayer;
+ } else {
+ human = settings?.playerX === PLAYER_TYPES.user ? PLAYER_X : PLAYER_O;
+ ai = switchPlayer(human);
+ }
+
+ const boardSize = settings?.boardSize ?? BOARD_SIZES._3x3;
+
+ let bestVal = -1000;
+ let row = -1;
+ let col = -1;
+
+ // Traverse all cells, evaluate
+ // minimax function for all empty
+ // cells. And return the cell
+ // with optimal value.
+ for (let i = 0; i < boardSize; i++) {
+ for (let j = 0; j < boardSize; j++) {
+ // Check if cell is empty
+ if (board[i][j] === null) {
+ // Make the move
+ board[i][j] = ai;
+
+ // compute evaluation function
+ // for this move.
+ let moveVal = minimax(board, 0, false, human, ai, boardSize);
+
+ // Undo the move
+ board[i][j] = null;
+
+ // If the value of the current move
+ // is more than the best value, then
+ // update best
+ if (moveVal > bestVal) {
+ row = i;
+ col = j;
+ bestVal = moveVal;
+ }
+ }
+ }
+ }
+
+ return [row, col];
+}
diff --git a/src/utils/alert.tsx b/src/utils/alert.tsx
index a67ebb9..178c27c 100644
--- a/src/utils/alert.tsx
+++ b/src/utils/alert.tsx
@@ -3,7 +3,7 @@ import { Alert } from "react-native";
export const displayAlert = (
title: string,
message: string,
- onClose?: () => void
+ onClose?: () => void,
) =>
Alert.alert(title, message, [
{
diff --git a/src/utils/common.ts b/src/utils/common.ts
index a420f7f..8b3fd4f 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,264 +1,264 @@
-import {
- TIE,
- PLAYER_O,
- PLAYER_X,
- PLAYER_TYPES,
- BOARD_SIZES,
-} from "../constants/common";
-import { GameBoard, GameSettings } from "../types/types";
-
-export const createBoard = (size: number) =>
- new Array(size).fill(Array(size).fill(null));
-
-// Get a random number between min and max
-export const getRandomNumber = (min: number, max: number) => {
- min = Math.ceil(min);
- max = Math.floor(max);
- return Math.floor(Math.random() * (max - min + 1)) + min;
-};
-
-export const switchPlayer = (player: number) => {
- return player === PLAYER_X ? PLAYER_O : PLAYER_X;
-};
-
-/**
- * This function is used to return a random move for the computer
- * @param board
- * @param boardSize
- * @returns
- */
-export const getRandomMove = (board: GameBoard, boardSize?: number | null) => {
- const max = (boardSize ?? BOARD_SIZES._3x3) - 1;
- let row = getRandomNumber(0, max);
- let col = getRandomNumber(0, max);
-
- while (board[row][col]) {
- row = getRandomNumber(0, max);
- col = getRandomNumber(0, max);
- }
-
- return [row, col];
-};
-
-/**
- * This function returns the game's final status based on player X and O, or tie
- * @param winner
- * @returns
- */
-export const getWinnerText = (winner: number) => {
- let winnerText = "";
- switch (winner) {
- case PLAYER_X:
- winnerText = "The winner is player X";
- break;
- case PLAYER_O:
- winnerText = "The winner is player O";
- break;
- case TIE:
- default:
- winnerText = "It's a tie";
- }
-
- return winnerText;
-};
-
-/**
- * This function calculates all winning possibilities, e.g. column, row, or diagonal in order to determine the winner
- *
- * TODO: This function can def be improved by calculating all possibilities dynamically - no hard coding
- * @param board
- * @param boardSize
- * @returns
- */
-export const getWinner = (board: GameBoard, boardSize?: number | null) => {
- const dimensions = boardSize ?? BOARD_SIZES._3x3;
-
- const rowMarked = (row: number, board: GameBoard) => {
- if (dimensions === BOARD_SIZES._3x3) {
- return (
- board[row][0] !== null &&
- board[row][0] === board[row][1] &&
- board[row][1] === board[row][2]
- );
- }
- if (dimensions === BOARD_SIZES._4x4) {
- return (
- board[row][0] !== null &&
- board[row][0] === board[row][1] &&
- board[row][1] === board[row][2] &&
- board[row][2] === board[row][3]
- );
- }
- if (dimensions === BOARD_SIZES._5x5) {
- return (
- board[row][0] !== null &&
- board[row][0] === board[row][1] &&
- board[row][1] === board[row][2] &&
- board[row][2] === board[row][3] &&
- board[row][3] === board[row][4]
- );
- }
- };
-
- const colMarked = (column: number, board: GameBoard) => {
- if (dimensions === BOARD_SIZES._3x3) {
- return (
- board[0][column] !== null &&
- board[0][column] === board[1][column] &&
- board[1][column] === board[2][column]
- );
- }
- if (dimensions === BOARD_SIZES._4x4) {
- return (
- board[0][column] !== null &&
- board[0][column] === board[1][column] &&
- board[1][column] === board[2][column] &&
- board[2][column] === board[3][column]
- );
- }
- if (dimensions === BOARD_SIZES._5x5) {
- return (
- board[0][column] !== null &&
- board[0][column] === board[1][column] &&
- board[1][column] === board[2][column] &&
- board[2][column] === board[3][column] &&
- board[3][column] === board[4][column]
- );
- }
- };
-
- const diagonalMarked = (diagonal: number, board: GameBoard) => {
- if (dimensions === BOARD_SIZES._3x3) {
- if (diagonal === 1) {
- return (
- board[0][0] !== null &&
- board[0][0] === board[1][1] &&
- board[1][1] === board[2][2]
- );
- } else {
- return (
- board[2][0] !== null &&
- board[2][0] === board[1][1] &&
- board[1][1] === board[0][2]
- );
- }
- }
- if (dimensions === BOARD_SIZES._4x4) {
- if (diagonal === 1) {
- return (
- board[0][0] !== null &&
- board[0][0] === board[1][1] &&
- board[1][1] === board[2][2] &&
- board[2][2] === board[3][3]
- );
- } else {
- return (
- board[2][0] !== null &&
- board[3][0] === board[2][1] &&
- board[2][1] === board[1][2] &&
- board[1][2] === board[0][3]
- );
- }
- }
- if (dimensions === BOARD_SIZES._5x5) {
- if (diagonal === 1) {
- return (
- board[0][0] !== null &&
- board[0][0] === board[1][1] &&
- board[1][1] === board[2][2] &&
- board[2][2] === board[3][3] &&
- board[3][3] === board[4][4]
- );
- } else {
- return (
- board[2][0] !== null &&
- board[4][0] === board[3][1] &&
- board[3][1] === board[2][2] &&
- board[2][2] === board[1][3] &&
- board[1][3] === board[0][4]
- );
- }
- }
- };
-
- if (dimensions === BOARD_SIZES._5x5 && rowMarked(4, board)) {
- return board[4][0];
- }
- if (dimensions === BOARD_SIZES._4x4 && rowMarked(3, board)) {
- return board[3][0];
- }
-
- if (dimensions === BOARD_SIZES._5x5 && colMarked(4, board)) {
- return board[0][4];
- }
- if (dimensions === BOARD_SIZES._4x4 && colMarked(3, board)) {
- return board[0][3];
- }
-
- if (dimensions === BOARD_SIZES._5x5 && diagonalMarked(2, board)) {
- return board[4][0];
- }
- if (dimensions === BOARD_SIZES._4x4 && diagonalMarked(2, board)) {
- return board[3][0];
- }
-
- if (rowMarked(0, board)) {
- return board[0][0];
- }
- if (rowMarked(1, board)) {
- return board[1][0];
- }
- if (rowMarked(2, board)) {
- return board[2][0];
- }
-
- if (colMarked(0, board)) {
- return board[0][0];
- }
- if (colMarked(1, board)) {
- return board[0][1];
- }
- if (colMarked(2, board)) {
- return board[0][2];
- }
-
- if (diagonalMarked(1, board)) {
- return board[0][0];
- }
- if (diagonalMarked(2, board)) {
- return board[2][0];
- }
-
- // if we get here, we need to check for any empty spot before we determine if game is a tie
- for (let row = 0; row < board.length; row++) {
- for (let col = 0; col < board[row].length; col++) {
- if (board[row][col] === null) {
- return null;
- }
- }
- }
-
- return TIE;
-};
-
-export const isUserVsUser = (settings: GameSettings) =>
- settings.playerX === PLAYER_TYPES.user &&
- settings.playerO === PLAYER_TYPES.user;
-
-export const isComputerVsComputer = (settings: GameSettings) =>
- settings.playerX === PLAYER_TYPES.computer &&
- settings.playerO === PLAYER_TYPES.computer;
-
-export const getUserValue = (settings: GameSettings) =>
- isUserVsUser(settings)
- ? PLAYER_X
- : settings.playerX === PLAYER_TYPES.user
- ? PLAYER_X
- : PLAYER_O;
-
-export const getComputerValue = (settings: GameSettings) =>
- isComputerVsComputer(settings)
- ? PLAYER_X
- : settings.playerO === PLAYER_TYPES.computer
- ? PLAYER_O
- : PLAYER_X;
+import {
+ TIE,
+ PLAYER_O,
+ PLAYER_X,
+ PLAYER_TYPES,
+ BOARD_SIZES,
+} from "../constants/common";
+import { GameBoard, GameSettings } from "../types/types";
+
+export const createBoard = (size: number) =>
+ new Array(size).fill(Array(size).fill(null));
+
+// Get a random number between min and max
+export const getRandomNumber = (min: number, max: number) => {
+ min = Math.ceil(min);
+ max = Math.floor(max);
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+};
+
+export const switchPlayer = (player: number) => {
+ return player === PLAYER_X ? PLAYER_O : PLAYER_X;
+};
+
+/**
+ * This function is used to return a random move for the computer
+ * @param board
+ * @param boardSize
+ * @returns
+ */
+export const getRandomMove = (board: GameBoard, boardSize?: number | null) => {
+ const max = (boardSize ?? BOARD_SIZES._3x3) - 1;
+ let row = getRandomNumber(0, max);
+ let col = getRandomNumber(0, max);
+
+ while (board[row][col]) {
+ row = getRandomNumber(0, max);
+ col = getRandomNumber(0, max);
+ }
+
+ return [row, col];
+};
+
+/**
+ * This function returns the game's final status based on player X and O, or tie
+ * @param winner
+ * @returns
+ */
+export const getWinnerText = (winner: number) => {
+ let winnerText = "";
+ switch (winner) {
+ case PLAYER_X:
+ winnerText = "The winner is player X";
+ break;
+ case PLAYER_O:
+ winnerText = "The winner is player O";
+ break;
+ case TIE:
+ default:
+ winnerText = "It's a tie";
+ }
+
+ return winnerText;
+};
+
+/**
+ * This function calculates all winning possibilities, e.g. column, row, or diagonal in order to determine the winner
+ *
+ * TODO: This function can def be improved by calculating all possibilities dynamically - no hard coding
+ * @param board
+ * @param boardSize
+ * @returns
+ */
+export const getWinner = (board: GameBoard, boardSize?: number | null) => {
+ const dimensions = boardSize ?? BOARD_SIZES._3x3;
+
+ const rowMarked = (row: number, board: GameBoard) => {
+ if (dimensions === BOARD_SIZES._3x3) {
+ return (
+ board[row][0] !== null &&
+ board[row][0] === board[row][1] &&
+ board[row][1] === board[row][2]
+ );
+ }
+ if (dimensions === BOARD_SIZES._4x4) {
+ return (
+ board[row][0] !== null &&
+ board[row][0] === board[row][1] &&
+ board[row][1] === board[row][2] &&
+ board[row][2] === board[row][3]
+ );
+ }
+ if (dimensions === BOARD_SIZES._5x5) {
+ return (
+ board[row][0] !== null &&
+ board[row][0] === board[row][1] &&
+ board[row][1] === board[row][2] &&
+ board[row][2] === board[row][3] &&
+ board[row][3] === board[row][4]
+ );
+ }
+ };
+
+ const colMarked = (column: number, board: GameBoard) => {
+ if (dimensions === BOARD_SIZES._3x3) {
+ return (
+ board[0][column] !== null &&
+ board[0][column] === board[1][column] &&
+ board[1][column] === board[2][column]
+ );
+ }
+ if (dimensions === BOARD_SIZES._4x4) {
+ return (
+ board[0][column] !== null &&
+ board[0][column] === board[1][column] &&
+ board[1][column] === board[2][column] &&
+ board[2][column] === board[3][column]
+ );
+ }
+ if (dimensions === BOARD_SIZES._5x5) {
+ return (
+ board[0][column] !== null &&
+ board[0][column] === board[1][column] &&
+ board[1][column] === board[2][column] &&
+ board[2][column] === board[3][column] &&
+ board[3][column] === board[4][column]
+ );
+ }
+ };
+
+ const diagonalMarked = (diagonal: number, board: GameBoard) => {
+ if (dimensions === BOARD_SIZES._3x3) {
+ if (diagonal === 1) {
+ return (
+ board[0][0] !== null &&
+ board[0][0] === board[1][1] &&
+ board[1][1] === board[2][2]
+ );
+ } else {
+ return (
+ board[2][0] !== null &&
+ board[2][0] === board[1][1] &&
+ board[1][1] === board[0][2]
+ );
+ }
+ }
+ if (dimensions === BOARD_SIZES._4x4) {
+ if (diagonal === 1) {
+ return (
+ board[0][0] !== null &&
+ board[0][0] === board[1][1] &&
+ board[1][1] === board[2][2] &&
+ board[2][2] === board[3][3]
+ );
+ } else {
+ return (
+ board[2][0] !== null &&
+ board[3][0] === board[2][1] &&
+ board[2][1] === board[1][2] &&
+ board[1][2] === board[0][3]
+ );
+ }
+ }
+ if (dimensions === BOARD_SIZES._5x5) {
+ if (diagonal === 1) {
+ return (
+ board[0][0] !== null &&
+ board[0][0] === board[1][1] &&
+ board[1][1] === board[2][2] &&
+ board[2][2] === board[3][3] &&
+ board[3][3] === board[4][4]
+ );
+ } else {
+ return (
+ board[2][0] !== null &&
+ board[4][0] === board[3][1] &&
+ board[3][1] === board[2][2] &&
+ board[2][2] === board[1][3] &&
+ board[1][3] === board[0][4]
+ );
+ }
+ }
+ };
+
+ if (dimensions === BOARD_SIZES._5x5 && rowMarked(4, board)) {
+ return board[4][0];
+ }
+ if (dimensions === BOARD_SIZES._4x4 && rowMarked(3, board)) {
+ return board[3][0];
+ }
+
+ if (dimensions === BOARD_SIZES._5x5 && colMarked(4, board)) {
+ return board[0][4];
+ }
+ if (dimensions === BOARD_SIZES._4x4 && colMarked(3, board)) {
+ return board[0][3];
+ }
+
+ if (dimensions === BOARD_SIZES._5x5 && diagonalMarked(2, board)) {
+ return board[4][0];
+ }
+ if (dimensions === BOARD_SIZES._4x4 && diagonalMarked(2, board)) {
+ return board[3][0];
+ }
+
+ if (rowMarked(0, board)) {
+ return board[0][0];
+ }
+ if (rowMarked(1, board)) {
+ return board[1][0];
+ }
+ if (rowMarked(2, board)) {
+ return board[2][0];
+ }
+
+ if (colMarked(0, board)) {
+ return board[0][0];
+ }
+ if (colMarked(1, board)) {
+ return board[0][1];
+ }
+ if (colMarked(2, board)) {
+ return board[0][2];
+ }
+
+ if (diagonalMarked(1, board)) {
+ return board[0][0];
+ }
+ if (diagonalMarked(2, board)) {
+ return board[2][0];
+ }
+
+ // if we get here, we need to check for any empty spot before we determine if game is a tie
+ for (let row = 0; row < board.length; row++) {
+ for (let col = 0; col < board[row].length; col++) {
+ if (board[row][col] === null) {
+ return null;
+ }
+ }
+ }
+
+ return TIE;
+};
+
+export const isUserVsUser = (settings: GameSettings) =>
+ settings.playerX === PLAYER_TYPES.user &&
+ settings.playerO === PLAYER_TYPES.user;
+
+export const isComputerVsComputer = (settings: GameSettings) =>
+ settings.playerX === PLAYER_TYPES.computer &&
+ settings.playerO === PLAYER_TYPES.computer;
+
+export const getUserValue = (settings: GameSettings) =>
+ isUserVsUser(settings)
+ ? PLAYER_X
+ : settings.playerX === PLAYER_TYPES.user
+ ? PLAYER_X
+ : PLAYER_O;
+
+export const getComputerValue = (settings: GameSettings) =>
+ isComputerVsComputer(settings)
+ ? PLAYER_X
+ : settings.playerO === PLAYER_TYPES.computer
+ ? PLAYER_O
+ : PLAYER_X;
diff --git a/yarn.lock b/yarn.lock
index 54a1ca1..d624373 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -797,6 +797,38 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
+ integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
+ dependencies:
+ eslint-visitor-keys "^3.3.0"
+
+"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1":
+ version "4.11.0"
+ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
+ integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
+
+"@eslint/eslintrc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad"
+ integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^9.6.0"
+ globals "^13.19.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
+
+"@eslint/js@8.57.0":
+ version "8.57.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f"
+ integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
+
"@expo/bunyan@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@expo/bunyan/-/bunyan-4.0.0.tgz#be0c1de943c7987a9fbd309ea0b1acd605890c7b"
@@ -1131,6 +1163,25 @@
dependencies:
"@hapi/hoek" "^9.0.0"
+"@humanwhocodes/config-array@^0.11.14":
+ version "0.11.14"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
+ integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==
+ dependencies:
+ "@humanwhocodes/object-schema" "^2.0.2"
+ debug "^4.3.1"
+ minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^2.0.2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
+ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+
"@isaacs/cliui@^8.0.2":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
@@ -1427,7 +1478,7 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
-"@nodelib/fs.walk@^1.2.3":
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@@ -1447,6 +1498,11 @@
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+"@pkgr/core@^0.1.0":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
+ integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
+
"@react-native-community/cli-clean@13.6.8":
version "13.6.8"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-13.6.8.tgz#95ce964047f005152ac100394b6dcd5d2cc2a474"
@@ -2000,6 +2056,11 @@
"@types/tough-cookie" "*"
parse5 "^7.0.0"
+"@types/json5@^0.0.29":
+ version "0.0.29"
+ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
+ integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
+
"@types/node-forge@^1.3.0":
version "1.3.11"
resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
@@ -2078,6 +2139,92 @@
dependencies:
"@types/yargs-parser" "*"
+"@typescript-eslint/eslint-plugin@^7.4.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz#b16d3cf3ee76bf572fdf511e79c248bdec619ea3"
+ integrity sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==
+ dependencies:
+ "@eslint-community/regexpp" "^4.10.0"
+ "@typescript-eslint/scope-manager" "7.18.0"
+ "@typescript-eslint/type-utils" "7.18.0"
+ "@typescript-eslint/utils" "7.18.0"
+ "@typescript-eslint/visitor-keys" "7.18.0"
+ graphemer "^1.4.0"
+ ignore "^5.3.1"
+ natural-compare "^1.4.0"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/parser@^7.4.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.18.0.tgz#83928d0f1b7f4afa974098c64b5ce6f9051f96a0"
+ integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==
+ dependencies:
+ "@typescript-eslint/scope-manager" "7.18.0"
+ "@typescript-eslint/types" "7.18.0"
+ "@typescript-eslint/typescript-estree" "7.18.0"
+ "@typescript-eslint/visitor-keys" "7.18.0"
+ debug "^4.3.4"
+
+"@typescript-eslint/scope-manager@7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz#c928e7a9fc2c0b3ed92ab3112c614d6bd9951c83"
+ integrity sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==
+ dependencies:
+ "@typescript-eslint/types" "7.18.0"
+ "@typescript-eslint/visitor-keys" "7.18.0"
+
+"@typescript-eslint/type-utils@7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz#2165ffaee00b1fbbdd2d40aa85232dab6998f53b"
+ integrity sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==
+ dependencies:
+ "@typescript-eslint/typescript-estree" "7.18.0"
+ "@typescript-eslint/utils" "7.18.0"
+ debug "^4.3.4"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/types@7.18.0", "@typescript-eslint/types@^7.2.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.18.0.tgz#b90a57ccdea71797ffffa0321e744f379ec838c9"
+ integrity sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==
+
+"@typescript-eslint/typescript-estree@7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz#b5868d486c51ce8f312309ba79bdb9f331b37931"
+ integrity sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==
+ dependencies:
+ "@typescript-eslint/types" "7.18.0"
+ "@typescript-eslint/visitor-keys" "7.18.0"
+ debug "^4.3.4"
+ globby "^11.1.0"
+ is-glob "^4.0.3"
+ minimatch "^9.0.4"
+ semver "^7.6.0"
+ ts-api-utils "^1.3.0"
+
+"@typescript-eslint/utils@7.18.0", "@typescript-eslint/utils@^7.2.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.18.0.tgz#bca01cde77f95fc6a8d5b0dbcbfb3d6ca4be451f"
+ integrity sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.4.0"
+ "@typescript-eslint/scope-manager" "7.18.0"
+ "@typescript-eslint/types" "7.18.0"
+ "@typescript-eslint/typescript-estree" "7.18.0"
+
+"@typescript-eslint/visitor-keys@7.18.0":
+ version "7.18.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz#0564629b6124d67607378d0f0332a0495b25e7d7"
+ integrity sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==
+ dependencies:
+ "@typescript-eslint/types" "7.18.0"
+ eslint-visitor-keys "^3.4.3"
+
+"@ungap/structured-clone@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
+ integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
+
"@urql/core@2.3.6":
version "2.3.6"
resolved "https://registry.yarnpkg.com/@urql/core/-/core-2.3.6.tgz#ee0a6f8fde02251e9560c5f17dce5cd90f948552"
@@ -2140,6 +2287,11 @@ acorn-globals@^7.0.0:
acorn "^8.1.0"
acorn-walk "^8.0.2"
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
acorn-walk@^8.0.2:
version "8.3.3"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e"
@@ -2147,7 +2299,7 @@ acorn-walk@^8.0.2:
dependencies:
acorn "^8.11.0"
-acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1, acorn@^8.8.2:
+acorn@^8.1.0, acorn@^8.11.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0:
version "8.12.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
@@ -2167,6 +2319,16 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
anser@^1.4.9:
version "1.4.10"
resolved "https://registry.yarnpkg.com/anser/-/anser-1.4.10.tgz#befa3eddf282684bd03b63dcda3927aef8c2e35b"
@@ -2280,11 +2442,78 @@ array-buffer-byte-length@^1.0.1:
call-bind "^1.0.5"
is-array-buffer "^3.0.4"
+array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8:
+ version "3.1.8"
+ resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d"
+ integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-object-atoms "^1.0.0"
+ get-intrinsic "^1.2.4"
+ is-string "^1.0.7"
+
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+array.prototype.findlast@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904"
+ integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ es-shim-unscopables "^1.0.2"
+
+array.prototype.findlastindex@^1.2.3:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d"
+ integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ es-shim-unscopables "^1.0.2"
+
+array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18"
+ integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.2.0"
+ es-abstract "^1.22.1"
+ es-shim-unscopables "^1.0.0"
+
+array.prototype.flatmap@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527"
+ integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==
+ dependencies:
+ call-bind "^1.0.2"
+ define-properties "^1.2.0"
+ es-abstract "^1.22.1"
+ es-shim-unscopables "^1.0.0"
+
+array.prototype.tosorted@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz#fe954678ff53034e717ea3352a03f0b0b86f7ffc"
+ integrity sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.3"
+ es-errors "^1.3.0"
+ es-shim-unscopables "^1.0.2"
+
arraybuffer.prototype.slice@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz#097972f4255e41bc3425e37dc3f6421cf9aefde6"
@@ -2972,7 +3201,7 @@ cross-spawn@^6.0.0:
shebang-command "^1.2.0"
which "^1.2.9"
-cross-spawn@^7.0.0, cross-spawn@^7.0.3:
+cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
@@ -3078,7 +3307,7 @@ debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
dependencies:
ms "2.1.2"
-debug@^3.1.0:
+debug@^3.1.0, debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
@@ -3105,6 +3334,11 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+deep-is@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
deepmerge@^4.2.2, deepmerge@^4.3.0:
version "4.3.1"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
@@ -3139,7 +3373,7 @@ define-lazy-prop@^2.0.0:
resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f"
integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==
-define-properties@^1.2.0, define-properties@^1.2.1:
+define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c"
integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==
@@ -3204,6 +3438,20 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
+doctrine@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
+ integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
+ dependencies:
+ esutils "^2.0.2"
+
+doctrine@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+ integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+ dependencies:
+ esutils "^2.0.2"
+
domexception@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
@@ -3265,6 +3513,14 @@ end-of-stream@^1.1.0:
dependencies:
once "^1.4.0"
+enhanced-resolve@^5.12.0:
+ version "5.17.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
+ integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
@@ -3307,7 +3563,7 @@ errorhandler@^1.5.1:
accepts "~1.3.7"
escape-html "~1.0.3"
-es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0:
+es-abstract@^1.17.5, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.1, es-abstract@^1.23.2, es-abstract@^1.23.3:
version "1.23.3"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0"
integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==
@@ -3371,6 +3627,26 @@ es-errors@^1.2.1, es-errors@^1.3.0:
resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+es-iterator-helpers@^1.0.19:
+ version "1.0.19"
+ resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8"
+ integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.3"
+ es-errors "^1.3.0"
+ es-set-tostringtag "^2.0.3"
+ function-bind "^1.1.2"
+ get-intrinsic "^1.2.4"
+ globalthis "^1.0.3"
+ has-property-descriptors "^1.0.2"
+ has-proto "^1.0.3"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.7"
+ iterator.prototype "^1.1.2"
+ safe-array-concat "^1.1.2"
+
es-object-atoms@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941"
@@ -3387,6 +3663,13 @@ es-set-tostringtag@^2.0.3:
has-tostringtag "^1.0.2"
hasown "^2.0.1"
+es-shim-unscopables@^1.0.0, es-shim-unscopables@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763"
+ integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==
+ dependencies:
+ hasown "^2.0.0"
+
es-to-primitive@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@@ -3432,12 +3715,207 @@ escodegen@^2.0.0:
optionalDependencies:
source-map "~0.6.1"
+eslint-config-expo@^7.0.0:
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/eslint-config-expo/-/eslint-config-expo-7.1.2.tgz#0ecb2c389b013dc3dfbf8a7401269a0589f03156"
+ integrity sha512-WxrDVNklN43Op0v3fglQfzL2bC7vqacUq9oVwJcGCUEDzdM7kGOR6pfEJiz3i3dQv3cFjHtct0CFEExep5c/dA==
+ dependencies:
+ "@typescript-eslint/eslint-plugin" "^7.4.0"
+ "@typescript-eslint/parser" "^7.4.0"
+ eslint-import-resolver-typescript "^3.6.1"
+ eslint-plugin-expo "^0.0.1"
+ eslint-plugin-import "^2.29.1"
+ eslint-plugin-react "^7.34.0"
+ eslint-plugin-react-hooks "^4.6.0"
+
+eslint-config-prettier@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f"
+ integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==
+
+eslint-import-resolver-node@^0.3.9:
+ version "0.3.9"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
+ integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==
+ dependencies:
+ debug "^3.2.7"
+ is-core-module "^2.13.0"
+ resolve "^1.22.4"
+
+eslint-import-resolver-typescript@^3.6.1:
+ version "3.6.1"
+ resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa"
+ integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==
+ dependencies:
+ debug "^4.3.4"
+ enhanced-resolve "^5.12.0"
+ eslint-module-utils "^2.7.4"
+ fast-glob "^3.3.1"
+ get-tsconfig "^4.5.0"
+ is-core-module "^2.11.0"
+ is-glob "^4.0.3"
+
+eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0:
+ version "2.8.1"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34"
+ integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==
+ dependencies:
+ debug "^3.2.7"
+
+eslint-plugin-expo@^0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-expo/-/eslint-plugin-expo-0.0.1.tgz#1164e9593c73619dca26cbc71c58889bd44b4861"
+ integrity sha512-dNri81vunJ3T+N1YWWxjLU6ux6KiukwZ4ECXCOPp8hG7M4kuvPAb9YQSIM63AT0pbtfYH/a6htikhaQcRPjhRA==
+ dependencies:
+ "@typescript-eslint/types" "^7.2.0"
+ "@typescript-eslint/utils" "^7.2.0"
+
+eslint-plugin-import@^2.29.1:
+ version "2.29.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643"
+ integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==
+ dependencies:
+ array-includes "^3.1.7"
+ array.prototype.findlastindex "^1.2.3"
+ array.prototype.flat "^1.3.2"
+ array.prototype.flatmap "^1.3.2"
+ debug "^3.2.7"
+ doctrine "^2.1.0"
+ eslint-import-resolver-node "^0.3.9"
+ eslint-module-utils "^2.8.0"
+ hasown "^2.0.0"
+ is-core-module "^2.13.1"
+ is-glob "^4.0.3"
+ minimatch "^3.1.2"
+ object.fromentries "^2.0.7"
+ object.groupby "^1.0.1"
+ object.values "^1.1.7"
+ semver "^6.3.1"
+ tsconfig-paths "^3.15.0"
+
+eslint-plugin-prettier@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz#d1c8f972d8f60e414c25465c163d16f209411f95"
+ integrity sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==
+ dependencies:
+ prettier-linter-helpers "^1.0.0"
+ synckit "^0.9.1"
+
+eslint-plugin-react-hooks@^4.6.0:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
+ integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
+
+eslint-plugin-react@^7.34.0:
+ version "7.35.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41"
+ integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==
+ dependencies:
+ array-includes "^3.1.8"
+ array.prototype.findlast "^1.2.5"
+ array.prototype.flatmap "^1.3.2"
+ array.prototype.tosorted "^1.1.4"
+ doctrine "^2.1.0"
+ es-iterator-helpers "^1.0.19"
+ estraverse "^5.3.0"
+ hasown "^2.0.2"
+ jsx-ast-utils "^2.4.1 || ^3.0.0"
+ minimatch "^3.1.2"
+ object.entries "^1.1.8"
+ object.fromentries "^2.0.8"
+ object.values "^1.2.0"
+ prop-types "^15.8.1"
+ resolve "^2.0.0-next.5"
+ semver "^6.3.1"
+ string.prototype.matchall "^4.0.11"
+ string.prototype.repeat "^1.0.0"
+
+eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint@^8.57.0:
+ version "8.57.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668"
+ integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/eslintrc" "^2.1.4"
+ "@eslint/js" "8.57.0"
+ "@humanwhocodes/config-array" "^0.11.14"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ "@ungap/structured-clone" "^1.2.0"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ doctrine "^3.0.0"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ esquery "^1.4.2"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ graphemer "^1.4.0"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-yaml "^4.1.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+espree@^9.6.0, espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
+ dependencies:
+ acorn "^8.9.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.1"
+
esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-estraverse@^5.2.0:
+esquery@^1.4.2:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
@@ -3589,7 +4067,17 @@ expo@~51.0.14:
fbemitter "^3.0.0"
whatwg-url-without-unicode "8.0.0-3"
-fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.2:
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-diff@^1.1.2:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
+ integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
+
+fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.1, fast-glob@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -3600,11 +4088,16 @@ fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.2:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-json-stable-stringify@^2.1.0:
+fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4:
version "4.4.1"
resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f"
@@ -3656,6 +4149,13 @@ fetch-retry@^4.1.1:
resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-4.1.1.tgz#fafe0bb22b54f4d0a9c788dff6dd7f8673ca63f3"
integrity sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==
+file-entry-cache@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+ integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+ dependencies:
+ flat-cache "^3.0.4"
+
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
@@ -3715,6 +4215,20 @@ find-yarn-workspace-root@~2.0.0:
dependencies:
micromatch "^4.0.2"
+flat-cache@^3.0.4:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
+ integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.3"
+ rimraf "^3.0.2"
+
+flatted@^3.2.9:
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
+ integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
+
flow-enums-runtime@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz#5bb0cd1b0a3e471330f4d109039b7eba5cb3e787"
@@ -3898,6 +4412,13 @@ get-symbol-description@^1.0.2:
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
+get-tsconfig@^4.5.0:
+ version "4.7.6"
+ resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.6.tgz#118fd5b7b9bae234cc7705a00cd771d7eb65d62a"
+ integrity sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==
+ dependencies:
+ resolve-pkg-maps "^1.0.0"
+
getenv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/getenv/-/getenv-1.0.0.tgz#874f2e7544fbca53c7a4738f37de8605c3fcfc31"
@@ -3910,6 +4431,13 @@ glob-parent@^5.1.2:
dependencies:
is-glob "^4.0.1"
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
glob@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -3962,6 +4490,13 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+globals@^13.19.0:
+ version "13.24.0"
+ resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171"
+ integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
+ dependencies:
+ type-fest "^0.20.2"
+
globalthis@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236"
@@ -3970,7 +4505,7 @@ globalthis@^1.0.3:
define-properties "^1.2.1"
gopd "^1.0.1"
-globby@^11.0.1:
+globby@^11.0.1, globby@^11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
@@ -3994,6 +4529,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.2.0,
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
graphql-tag@^2.10.1:
version "2.12.6"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"
@@ -4147,7 +4687,7 @@ ieee754@^1.1.13:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-ignore@^5.2.0:
+ignore@^5.2.0, ignore@^5.3.1:
version "5.3.1"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
@@ -4167,6 +4707,14 @@ import-fresh@^2.0.0:
caller-path "^2.0.0"
resolve-from "^3.0.0"
+import-fresh@^3.2.1:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+ integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
import-local@^3.0.2:
version "3.2.0"
resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260"
@@ -4250,6 +4798,13 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+is-async-function@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646"
+ integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -4275,7 +4830,7 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
-is-core-module@^2.13.0:
+is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1:
version "2.15.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea"
integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==
@@ -4289,7 +4844,7 @@ is-data-view@^1.0.1:
dependencies:
is-typed-array "^1.1.13"
-is-date-object@^1.0.1:
+is-date-object@^1.0.1, is-date-object@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
@@ -4316,6 +4871,13 @@ is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+is-finalizationregistry@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6"
+ integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==
+ dependencies:
+ call-bind "^1.0.2"
+
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
@@ -4331,6 +4893,13 @@ is-generator-fn@^2.0.0:
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
+is-generator-function@^1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72"
+ integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==
+ dependencies:
+ has-tostringtag "^1.0.0"
+
is-glob@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@@ -4338,7 +4907,7 @@ is-glob@^2.0.0:
dependencies:
is-extglob "^1.0.0"
-is-glob@^4.0.1:
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@@ -4357,6 +4926,11 @@ is-invalid-path@^0.1.0:
dependencies:
is-glob "^2.0.0"
+is-map@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
+ integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
+
is-negative-zero@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747"
@@ -4379,7 +4953,7 @@ is-path-cwd@^2.2.0:
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb"
integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==
-is-path-inside@^3.0.2:
+is-path-inside@^3.0.2, is-path-inside@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
@@ -4404,6 +4978,11 @@ is-regex@^1.1.4:
call-bind "^1.0.2"
has-tostringtag "^1.0.0"
+is-set@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d"
+ integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
+
is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz#1237f1cba059cdb62431d378dcc37d9680181688"
@@ -4454,6 +5033,11 @@ is-valid-path@^0.1.1:
dependencies:
is-invalid-path "^0.1.0"
+is-weakmap@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd"
+ integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==
+
is-weakref@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
@@ -4461,6 +5045,14 @@ is-weakref@^1.0.2:
dependencies:
call-bind "^1.0.2"
+is-weakset@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.3.tgz#e801519df8c0c43e12ff2834eead84ec9e624007"
+ integrity sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==
+ dependencies:
+ call-bind "^1.0.7"
+ get-intrinsic "^1.2.4"
+
is-wsl@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
@@ -4546,6 +5138,17 @@ istanbul-reports@^3.1.3:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
+iterator.prototype@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0"
+ integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==
+ dependencies:
+ define-properties "^1.2.1"
+ get-intrinsic "^1.2.1"
+ has-symbols "^1.0.3"
+ reflect.getprototypeof "^1.0.4"
+ set-function-name "^2.0.1"
+
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
@@ -5085,6 +5688,11 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
json-parse-better-errors@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -5109,6 +5717,23 @@ json-schema-deref-sync@^0.13.0:
traverse "~0.6.6"
valid-url "~1.0.9"
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+json5@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593"
+ integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
+ dependencies:
+ minimist "^1.2.0"
+
json5@^2.2.2, json5@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
@@ -5130,6 +5755,23 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
+"jsx-ast-utils@^2.4.1 || ^3.0.0":
+ version "3.3.5"
+ resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
+ integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==
+ dependencies:
+ array-includes "^3.1.6"
+ array.prototype.flat "^1.3.1"
+ object.assign "^4.1.4"
+ object.values "^1.1.6"
+
+keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
kind-of@^6.0.2:
version "6.0.3"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
@@ -5145,6 +5787,14 @@ leven@^3.1.0:
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
lighthouse-logger@^1.0.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz#aef90f9e97cd81db367c7634292ee22079280aaa"
@@ -5241,6 +5891,11 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
lodash.throttle@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
@@ -5612,7 +6267,7 @@ min-indent@^1.0.0:
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
-"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1:
+"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -5860,7 +6515,7 @@ object-keys@^1.1.1:
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-object.assign@^4.1.5:
+object.assign@^4.1.4, object.assign@^4.1.5:
version "4.1.5"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0"
integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==
@@ -5870,6 +6525,43 @@ object.assign@^4.1.5:
has-symbols "^1.0.3"
object-keys "^1.1.1"
+object.entries@^1.1.8:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41"
+ integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
+object.fromentries@^2.0.7, object.fromentries@^2.0.8:
+ version "2.0.8"
+ resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65"
+ integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-object-atoms "^1.0.0"
+
+object.groupby@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e"
+ integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+
+object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b"
+ integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-object-atoms "^1.0.0"
+
on-finished@2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
@@ -5934,6 +6626,18 @@ open@^8.0.4, open@^8.3.0:
is-docker "^2.1.1"
is-wsl "^2.2.0"
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.5"
+
ora@3.4.0, ora@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318"
@@ -6036,6 +6740,13 @@ package-json-from-dist@^1.0.0:
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00"
integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
parse-json@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
@@ -6191,6 +6902,23 @@ postcss@~8.4.32:
picocolors "^1.0.1"
source-map-js "^1.2.0"
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier-linter-helpers@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
+ integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
+ dependencies:
+ fast-diff "^1.1.2"
+
+prettier@^3.3.3:
+ version "3.3.3"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
+ integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==
+
pretty-bytes@5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
@@ -6269,7 +6997,7 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-punycode@^2.1.1:
+punycode@^2.1.0, punycode@^2.1.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
@@ -6461,6 +7189,19 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+reflect.getprototypeof@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859"
+ integrity sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.1"
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.4"
+ globalthis "^1.0.3"
+ which-builtin-type "^1.1.3"
+
regenerate-unicode-properties@^10.1.0:
version "10.1.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480"
@@ -6558,17 +7299,27 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
resolve-from@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+resolve-pkg-maps@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
+ integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
+
resolve.exports@^2.0.0, resolve.exports@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800"
integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==
-resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.2:
+resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.2, resolve@^1.22.4:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -6577,6 +7328,15 @@ resolve@^1.14.2, resolve@^1.20.0, resolve@^1.22.2:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
+resolve@^2.0.0-next.5:
+ version "2.0.0-next.5"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c"
+ integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==
+ dependencies:
+ is-core-module "^2.13.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
resolve@~1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
@@ -6779,7 +7539,7 @@ set-function-length@^1.2.1:
gopd "^1.0.1"
has-property-descriptors "^1.0.2"
-set-function-name@^2.0.1:
+set-function-name@^2.0.1, set-function-name@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985"
integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==
@@ -6835,7 +7595,7 @@ shell-quote@^1.6.1, shell-quote@^1.7.3:
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
-side-channel@^1.0.4:
+side-channel@^1.0.4, side-channel@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
@@ -7054,6 +7814,32 @@ string-width@^5.0.1, string-width@^5.1.2:
emoji-regex "^9.2.2"
strip-ansi "^7.0.1"
+string.prototype.matchall@^4.0.11:
+ version "4.0.11"
+ resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a"
+ integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==
+ dependencies:
+ call-bind "^1.0.7"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.2"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.0.0"
+ get-intrinsic "^1.2.4"
+ gopd "^1.0.1"
+ has-symbols "^1.0.3"
+ internal-slot "^1.0.7"
+ regexp.prototype.flags "^1.5.2"
+ set-function-name "^2.0.2"
+ side-channel "^1.0.6"
+
+string.prototype.repeat@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a"
+ integrity sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==
+ dependencies:
+ define-properties "^1.1.3"
+ es-abstract "^1.17.5"
+
string.prototype.trim@^1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4"
@@ -7124,6 +7910,11 @@ strip-ansi@^7.0.1:
dependencies:
ansi-regex "^6.0.1"
+strip-bom@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+ integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
+
strip-bom@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
@@ -7233,6 +8024,19 @@ symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+synckit@^0.9.1:
+ version "0.9.1"
+ resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.1.tgz#febbfbb6649979450131f64735aa3f6c14575c88"
+ integrity sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==
+ dependencies:
+ "@pkgr/core" "^0.1.0"
+ tslib "^2.6.2"
+
+tapable@^2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
+ integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
+
tar@^6.0.5, tar@^6.1.11:
version "6.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
@@ -7406,16 +8210,38 @@ traverse@~0.6.6:
typedarray.prototype.slice "^1.0.3"
which-typed-array "^1.1.15"
+ts-api-utils@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
+ integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
+
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
-tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0:
+tsconfig-paths@^3.15.0:
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4"
+ integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==
+ dependencies:
+ "@types/json5" "^0.0.29"
+ json5 "^1.0.2"
+ minimist "^1.2.6"
+ strip-bom "^3.0.0"
+
+tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
type-detect@4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
@@ -7426,6 +8252,11 @@ type-fest@^0.16.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.16.0.tgz#3240b891a78b0deae910dbeb86553e552a148860"
integrity sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
type-fest@^0.21.3:
version "0.21.3"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
@@ -7611,6 +8442,13 @@ update-browserslist-db@^1.1.0:
escalade "^3.1.2"
picocolors "^1.0.1"
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
url-join@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.0.tgz#4d3340e807d3773bda9991f8305acdcc2a665d2a"
@@ -7764,6 +8602,34 @@ which-boxed-primitive@^1.0.2:
is-string "^1.0.5"
is-symbol "^1.0.3"
+which-builtin-type@^1.1.3:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3"
+ integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==
+ dependencies:
+ function.prototype.name "^1.1.6"
+ has-tostringtag "^1.0.2"
+ is-async-function "^2.0.0"
+ is-date-object "^1.0.5"
+ is-finalizationregistry "^1.0.2"
+ is-generator-function "^1.0.10"
+ is-regex "^1.1.4"
+ is-weakref "^1.0.2"
+ isarray "^2.0.5"
+ which-boxed-primitive "^1.0.2"
+ which-collection "^1.0.2"
+ which-typed-array "^1.1.15"
+
+which-collection@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0"
+ integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
+ dependencies:
+ is-map "^2.0.3"
+ is-set "^2.0.3"
+ is-weakmap "^2.0.2"
+ is-weakset "^2.0.3"
+
which-module@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
@@ -7804,6 +8670,11 @@ wonka@^6.3.2:
resolved "https://registry.yarnpkg.com/wonka/-/wonka-6.3.4.tgz#76eb9316e3d67d7febf4945202b5bdb2db534594"
integrity sha512-CjpbqNtBGNAeyNS/9W6q3kSkKE52+FjIj7AkFlLr11s/VWGUu6a2CdYSdGxocIhIVjaW/zchesBQUKPVU69Cqg==
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"