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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CodSpeed

on:
push:
branches:
- main
pull_request:
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:

permissions:
contents: read
id-token: write

jobs:
codspeed:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credential persistence through GitHub Actions artifacts [zizmor:zizmor/artipacked]


- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Run benchmarks
uses: CodSpeedHQ/action@v4

Check warning on line 32 in .github/workflows/codspeed.yml

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.github/workflows/codspeed.yml#L32

An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unpinned action reference [zizmor:zizmor/unpinned-uses]

with:
mode: simulation
run: npx vitest bench --run
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
![GitHub Created At](https://img.shields.io/github/created-at/LCSOGthb/Games)
![GitHub last commit](https://img.shields.io/github/last-commit/LCSOGthb/Games)

CodSpeed
[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/LCSOGthb/Games?utm_source=badge)

CodeRabbit
![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/LCSOGthb/Games?utm_source=oss&utm_medium=github&utm_campaign=LCSOGthb%2FGames&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)

Expand Down
46 changes: 1 addition & 45 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,5 @@
import GameCard from "@/components/GameCard";

const games = [
{
id: 1,
title: "Chess Master",
description: "Play chess against AI or friends online in real time.",
image: "/placeholder.svg?height=200&width=300",
category: "Strategy",
},
{
id: 2,
title: "Quick Quiz",
description: "Test your knowledge with fun trivia questions.",
image: "/placeholder.svg?height=200&width=300",
category: "Trivia",
},
{
id: 3,
title: "Memory Match",
description: "Challenge your memory with this classic card-matching game.",
image: "/placeholder.svg?height=200&width=300",
category: "Puzzle",
},
{
id: 4,
title: "Snake Game",
description: "Eat food, grow longer, and avoid running into yourself.",
image: "/placeholder.svg?height=200&width=300",
category: "Arcade",
},
{
id: 5,
title: "Word Builder",
description: "Create as many words as you can from scrambled letters.",
image: "/placeholder.svg?height=200&width=300",
category: "Word",
},
{
id: 6,
title: "Puzzle Solver",
description: "Solve challenging jigsaw puzzles of increasing difficulty.",
image: "/placeholder.svg?height=200&width=300",
category: "Puzzle",
},
];
import { games } from "@/lib/games";

Check warning on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

Can't resolve '@/lib/games' in '/src/app'

Check warning on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

ES2015 modules are forbidden.

Check notice on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

Expected 'multiple' syntax before 'single' syntax.

Check warning on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

Reaching to "@/lib/games" is not allowed.

Check warning on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move `page.tsx` to same directory as `@/lib/games` or consider making `@/lib/games` a package.

Check notice on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

Replace `"@/lib/games"` with `'@/lib/games'`

Check notice on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

There should be no space after '{'.

Check notice on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

There should be no space after '{'.

Check notice on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

There should be no space after '{'.

Check notice on line 2 in app/page.tsx

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

app/page.tsx#L2

There should be no space after '{'.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime error due to lack of validation for imported games array.
If games is undefined, null, or not an array, the component will fail at runtime when calling games.map. Consider validating the imported data before usage:

const validGames = Array.isArray(games) ? games : [];

Or handle errors gracefully to improve robustness.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Low

Speculative: The extracted utility functions (filterByCategory, searchGames, sortByTitle, etc.) in lib/games.ts are thoroughly benchmarked but are not imported or used anywhere in the application code (the page only consumes the games array). This creates a gap between the code that is performance‑tested and the code that is actually executed in production. If these functions are intended for future use, consider adding a small integration example (e.g., a search bar) to validate real‑world behavior. If they are not planned for immediate use, the benchmarks are still valid for baseline tracking, but the mismatch should be acknowledged.


export default function Home() {
return (
Expand Down
95 changes: 95 additions & 0 deletions bench/games.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { bench, describe } from "vitest";

Check warning on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Can't resolve 'vitest' in '/src/bench'

Check warning on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Dependency vitest cannot be verified because no package.json file was found.

Check warning on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

ES2015 modules are forbidden.

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Filename 'games.bench.ts' does not match the naming convention.

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Filename games.bench.ts does not match the file name pattern ^([a-z][a-z0-9]*)(-[a-z0-9]+)*(.spec|.test)?.ts$

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Imports should be sorted.

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Missing @file

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Replace `"vitest"` with `'vitest'`

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Run autofix to sort these imports!

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

There should be no space after '{'.

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

There should be no space after '{'.

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

There should be no space after '{'.

Check notice on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

There should be no space after '{'.

Check warning on line 1 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L1

Unable to resolve path to module 'vitest'.
import {

Check warning on line 2 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L2

ES2015 modules are forbidden.

Check notice on line 2 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L2

Expected "@/lib/games" to come before "vitest".
games,

Check notice on line 3 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L3

Import members should be sorted alphabetically.
filterByCategory,

Check notice on line 4 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L4

Expected "filterByCategory" to come before "games".

Check notice on line 4 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L4

Member 'filterByCategory' of the import declaration should be sorted alphabetically.

Check notice on line 4 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L4

Member 'filterByCategory' of the import declaration should be sorted alphabetically.
searchGames,
sortByTitle,
getCategories,

Check notice on line 7 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L7

Expected "getCategories" to come before "sortByTitle".
getGameById,

Check notice on line 8 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L8

Unexpected trailing comma.

Check notice on line 8 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L8

Unexpected trailing comma.

Check notice on line 8 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L8

Unexpected trailing comma.
} from "@/lib/games";

Check warning on line 9 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L9

Can't resolve '@/lib/games' in '/src/bench'

Check warning on line 9 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L9

Reaching to "@/lib/games" is not allowed.

Check warning on line 9 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L9

Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move `games.bench.ts` to same directory as `@/lib/games` or consider making `@/lib/games` a package.

Check notice on line 9 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L9

Replace `"@/lib/games"` with `'@/lib/games'`
import type { Game } from "@/lib/games";

Check notice on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

'@/lib/games' import is duplicated.

Check warning on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

Can't resolve '@/lib/games' in '/src/bench'

Check warning on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

ES2015 modules are forbidden.

Check notice on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

Expected "@/lib/games" to come before "@/lib/games".

Check warning on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

Identifier 'Game' does not match the pattern '^[a-z]+([A-Z][a-z]+)*$'.

Check warning on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

Reaching to "@/lib/games" is not allowed.

Check warning on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move `games.bench.ts` to same directory as `@/lib/games` or consider making `@/lib/games` a package.

Check notice on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

Replace `"@/lib/games"` with `'@/lib/games'`

Check notice on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

There should be no space after '{'.

Check notice on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

There should be no space after '{'.

Check notice on line 10 in bench/games.bench.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

bench/games.bench.ts#L10

There should be no space after '{'.

// Build a larger dataset from the base games for more realistic benchmarks
function generateLargeGameList(size: number): Game[] {
const categories = [
"Strategy",
"Trivia",
"Puzzle",
"Arcade",
"Word",
"RPG",
"Action",
"Simulation",
];
const result: Game[] = [];
for (let i = 0; i < size; i++) {
result.push({
id: i + 1,
title: `Game ${i + 1}`,
description: `Description for game number ${i + 1} with various keywords and details.`,
image: `/placeholder.svg?height=200&width=300`,
category: categories[i % categories.length],
});
}
return result;
}
Comment on lines +13 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dataset Realism for Benchmarks

The generateLargeGameList function creates games with unique titles and descriptions. This may not accurately reflect real-world scenarios where games can have similar or duplicate titles and descriptions. For more realistic benchmarking, consider introducing some repeated or similar values in the dataset to better simulate actual search and sort conditions.

Recommended solution:
Modify the dataset generation to include some duplicate or similar titles/descriptions:

const title = i % 10 === 0 ? "Game Special" : `Game ${i + 1}`;
const description = i % 5 === 0 ? "Common description" : `Description for game number ${i + 1}`;


const largeGameList = generateLargeGameList(1000);

describe("filterByCategory", () => {
bench("filter base games by category", () => {
filterByCategory(games, "Puzzle");
});
Comment on lines +39 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 | Confidence: Medium

Speculative: The benchmarks for filterByCategory and other functions cover only positive, matching scenarios. Missing benchmarks for edge cases such as filtering with a non‑existent category, searching with no results (already covered in searchGames), or handling an empty input array. Adding these would ensure that performance characteristics under non‑match and boundary conditions are tracked, preventing future regressions in those paths.

Code Suggestion:

bench("filter 1000 games with no match", () => {
  filterByCategory(largeGameList, "NonExistentCategory");
});
bench("filter empty list", () => {
  filterByCategory([], "Puzzle");
});


bench("filter 1000 games by category", () => {
filterByCategory(largeGameList, "Puzzle");
});
});

describe("searchGames", () => {
bench("search base games by title", () => {
searchGames(games, "chess");
});

bench("search 1000 games by keyword", () => {
searchGames(largeGameList, "keywords");
});

bench("search with no results", () => {
searchGames(largeGameList, "nonexistent-query-string");
});
});

describe("sortByTitle", () => {
bench("sort base games ascending", () => {
sortByTitle(games);
});

bench("sort 1000 games ascending", () => {
sortByTitle(largeGameList);
});

bench("sort 1000 games descending", () => {
sortByTitle(largeGameList, false);
});
});

describe("getCategories", () => {
bench("get categories from base games", () => {
getCategories(games);
});

bench("get categories from 1000 games", () => {
getCategories(largeGameList);
});
});

describe("getGameById", () => {
bench("find first game by id", () => {
getGameById(games, 1);
});

bench("find last game by id in 1000 games", () => {
getGameById(largeGameList, 1000);
});
});
Comment on lines +37 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing Edge Case Benchmarks

The current benchmarks do not test edge cases such as empty arrays or invalid input values. Including such cases can help ensure the robustness and error handling of the library functions under test.

Recommended solution:
Add additional benchmarks for empty arrays and invalid inputs:

describe("edge cases", () => {
  bench("filter empty array", () => {
    filterByCategory([], "Puzzle");
  });
  bench("search empty array", () => {
    searchGames([], "chess");
  });
  bench("sort empty array", () => {
    sortByTitle([]);
  });
  bench("get categories from empty array", () => {
    getCategories([]);
  });
  bench("get game by invalid id", () => {
    getGameById(games, -1);
  });
});

82 changes: 82 additions & 0 deletions lib/games.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
export interface Game {
id: number;
title: string;
description: string;
image: string;
category: string;
}

export const games: Game[] = [
{
id: 1,
title: "Chess Master",
description: "Play chess against AI or friends online in real time.",
image: "/placeholder.svg?height=200&width=300",
category: "Strategy",
},
{
id: 2,
title: "Quick Quiz",
description: "Test your knowledge with fun trivia questions.",
image: "/placeholder.svg?height=200&width=300",
category: "Trivia",
},
{
id: 3,
title: "Memory Match",
description: "Challenge your memory with this classic card-matching game.",
image: "/placeholder.svg?height=200&width=300",
category: "Puzzle",
},
{
id: 4,
title: "Snake Game",
description: "Eat food, grow longer, and avoid running into yourself.",
image: "/placeholder.svg?height=200&width=300",
category: "Arcade",
},
{
id: 5,
title: "Word Builder",
description: "Create as many words as you can from scrambled letters.",
image: "/placeholder.svg?height=200&width=300",
category: "Word",
},
{
id: 6,
title: "Puzzle Solver",
description: "Solve challenging jigsaw puzzles of increasing difficulty.",
image: "/placeholder.svg?height=200&width=300",
category: "Puzzle",
},
];
Comment on lines +9 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Exported games array is mutable — consumers could accidentally modify shared state

The games array at lib/games.ts:9 is exported as a mutable const (only the binding is const, not the contents). Any consumer could call games.push(...) or games[0].title = '...' and corrupt the shared data. Currently no consumer mutates it — app/page.tsx only reads it, and all utility functions (sortByTitle spreads before sorting, filter creates new arrays) are careful not to mutate. However, since this is now a shared module intended for broader use, consider using as const or Object.freeze to prevent accidental mutation in the future.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


export function filterByCategory(gameList: Game[], category: string): Game[] {
return gameList.filter(
(game) => game.category.toLowerCase() === category.toLowerCase(),
);
}
Comment on lines +54 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider normalizing/cleaning the category input (e.g. trimming) before comparison.

Currently only case-insensitive differences are handled. If category has leading/trailing whitespace or similar minor formatting differences, the filter will return no matches. Normalizing once (e.g. const normalized = category.trim().toLowerCase();) and using that for comparison would make this more robust to common input variations.

Suggested change
export function filterByCategory(gameList: Game[], category: string): Game[] {
return gameList.filter(
(game) => game.category.toLowerCase() === category.toLowerCase(),
);
}
export function filterByCategory(gameList: Game[], category: string): Game[] {
const normalizedCategory = category.trim().toLowerCase();
return gameList.filter((game) => {
const gameCategory = game.category?.trim().toLowerCase();
return gameCategory === normalizedCategory;
});
}


export function searchGames(gameList: Game[], query: string): Game[] {
const lowerQuery = query.toLowerCase();
return gameList.filter(
(game) =>
game.title.toLowerCase().includes(lowerQuery) ||
game.description.toLowerCase().includes(lowerQuery),
);
}
Comment on lines +60 to +67

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: searchGames returns all results when called with an empty string

In lib/games.ts:60-67, searchGames with query="" will return all games because ''.toLowerCase() is '' and every string .includes('') is true. This is a common convention (empty query = no filter), so it's not necessarily wrong, but it's worth being aware of this behavior since it's implicit rather than documented. If the intent is to require a non-empty query, a guard clause would be needed.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


export function sortByTitle(gameList: Game[], ascending = true): Game[] {
return [...gameList].sort((a, b) => {
const cmp = a.title.localeCompare(b.title);
return ascending ? cmp : -cmp;
});
}

export function getCategories(gameList: Game[]): string[] {
return [...new Set(gameList.map((game) => game.category))].sort();
}

export function getGameById(gameList: Game[], id: number): Game | undefined {
return gameList.find((game) => game.id === id);
}
Loading
Loading