Skip to content

Commit cc92528

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/client/typescript-5.9.2
2 parents 2c6da31 + cf6ca5b commit cc92528

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1847
-928
lines changed

.devcontainer/devcontainer.json

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,43 @@
1+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
2+
// README at: https://github.com/devcontainers/templates/tree/main/src/python
13
{
2-
"name": "Tailspin Toys Dev Container",
3-
"image": "mcr.microsoft.com/devcontainers/universal:latest",
4-
"postCreateCommand": "./scripts/setup-env.sh",
5-
"customizations": {
6-
"vscode": {
7-
"extensions": [
8-
"ms-dotnettools.csharp",
9-
"GitHub.copilot",
10-
"GitHub.copilot-chat",
11-
"ms-python.vscode-pylance",
12-
"svelte.svelte-vscode",
13-
"astro-build.astro-vscode"
14-
]
15-
}
16-
},
17-
"features": {
18-
"ghcr.io/devcontainers/features/dotnet:2": {
19-
"version": "9.0"
20-
}
21-
}
4+
"name": "Python 3",
5+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6+
"image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye",
7+
"features": {
8+
"ghcr.io/devcontainers/features/node:1": {},
9+
"ghcr.io/schlich/devcontainer-features/playwright:0": {},
10+
"ghcr.io/devcontainers-extra/features/typescript:2": {}
11+
},
12+
"postCreateCommand": "./scripts/setup-env.sh",
13+
"customizations": {
14+
"vscode": {
15+
"extensions": [
16+
"ms-dotnettools.csharp",
17+
"GitHub.copilot",
18+
"GitHub.copilot-chat",
19+
"ms-python.vscode-pylance",
20+
"svelte.svelte-vscode",
21+
"astro-build.astro-vscode"
22+
]
23+
}
24+
},
25+
26+
// Features to add to the dev container. More info: https://containers.dev/features.
27+
// "features": {},
28+
29+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
30+
"forwardPorts": [
31+
4321,
32+
5100
33+
],
34+
35+
// Use 'postCreateCommand' to run commands after the container is created.
36+
// "postCreateCommand": "pip3 install --user -r requirements.txt",
37+
38+
// Configure tool-specific properties.
39+
// "customizations": {},
40+
41+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
42+
// "remoteUser": "root"
2243
}

.github/copilot-instructions.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
This is a crowdfunding platform for games with a developer theme. The application uses a Flask backend API with SQLAlchemy ORM for database interactions, and an Astro/Svelte frontend with Tailwind CSS for styling. Please follow these guidelines when contributing:
44

5+
## Agent notes
6+
7+
- Do not generate summary markdown files upon completion of a task
8+
- Always use absolute paths when running scripts and BASH commands
9+
510
## Code standards
611

712
### Required Before Each Commit
@@ -46,7 +51,7 @@ This is a crowdfunding platform for games with a developer theme. The applicatio
4651
## Scripts
4752

4853
- Several scripts exist in the `scripts` folder
49-
- Use existing scripts to perform tasks rather than performing them manually
54+
- Always use available scripts to perform tasks rather than performing operations manually
5055
- Existing scripts:
5156
- `scripts/setup-env.sh`: Performs installation of all Python and Node dependencies
5257
- `scripts/run-server-tests.sh`: Calls setup-env, then runs all Python tests

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ updates:
2121
dependency-type: production
2222
update-types:
2323
- patch
24+
- package-ecosystem: "devcontainers"
25+
directory: "/"
26+
schedule:
27+
interval: weekly

.github/workflows/copilot-setup-steps.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ jobs:
1212
contents: read
1313
steps:
1414
- name: Checkout code
15-
uses: actions/checkout@v4
15+
uses: actions/checkout@v5
1616

1717
# Backend setup - Python
1818
- name: Set up Python
19-
uses: actions/setup-python@v4
19+
uses: actions/setup-python@v6
2020
with:
2121
python-version: "3.13"
2222
cache: "pip"
@@ -26,7 +26,7 @@ jobs:
2626

2727
# Frontend setup - Node.js
2828
- name: Set up Node.js
29-
uses: actions/setup-node@v4
29+
uses: actions/setup-node@v6
3030
with:
3131
node-version: "22"
3232
cache: "npm"
@@ -35,3 +35,7 @@ jobs:
3535
- name: Install JavaScript dependencies
3636
working-directory: ./client
3737
run: npm ci
38+
39+
- name: Install Playwright
40+
working-directory: ./client
41+
run: npx playwright install

.vscode/.copilotignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- docs/**/*

.vscode/settings.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,15 @@
99
"devcontainer",
1010
"devcontainers",
1111
"docstrings",
12+
"dotnettools",
1213
"isouter",
1314
"prebuild",
15+
"pylance",
16+
"schlich",
1417
"SDLC"
1518
],
1619
"github.copilot.nextEditSuggestions.enabled": true,
1720
"editor.tabSize": 4,
18-
"chat.agent.enabled": true
21+
"chat.agent.enabled": true,
22+
"chat.agent.maxRequests": 100,
1923
}

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
This repository contains the project for a 1 hour guided workshop to explore GitHub Copilot Agent Mode and related features in Visual Studio Code. The project is a website for a fictional game crowd-funding company, with a [Flask](https://flask.palletsprojects.com/en/stable/) backend using [SQLAlchemy](https://www.sqlalchemy.org/) and [Astro](https://astro.build/) frontend using [Svelte](https://svelte.dev/) for dynamic pages.
44

5-
To begin the workshop, start at [docs/README.md](./docs/README.md)
5+
## Start the workshop
6+
7+
**To begin the workshop, start at [docs/README.md](./docs/README.md)**
68

79
Or, if just want to run the app...
810

client/e2e-tests/games.spec.ts

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,35 @@ test.describe('Game Listing and Navigation', () => {
44
test('should display games with titles on index page', async ({ page }) => {
55
await page.goto('/');
66

7-
// Wait for the games to load
8-
await page.waitForSelector('[data-testid="games-grid"]', { timeout: 10000 });
7+
// Wait for the games to load using a more specific locator
8+
const gamesGrid = page.getByTestId('games-grid');
9+
await expect(gamesGrid).toBeVisible();
910

1011
// Check that games are displayed
11-
const gameCards = page.locator('[data-testid="game-card"]');
12+
const gameCards = page.getByTestId('game-card');
1213

1314
// Wait for at least one game card to be visible
1415
await expect(gameCards.first()).toBeVisible();
1516

1617
// Check that we have at least one game
17-
const gameCount = await gameCards.count();
18-
expect(gameCount).toBeGreaterThan(0);
18+
expect(await gameCards.count()).toBeGreaterThan(0);
1919

2020
// Check that each game card has a title
21-
const firstGameCard = gameCards.first();
22-
await expect(firstGameCard.locator('[data-testid="game-title"]')).toBeVisible();
21+
await expect(gameCards.first().getByTestId('game-title')).toBeVisible();
2322

2423
// Verify that game titles are not empty
25-
const gameTitle = await firstGameCard.locator('[data-testid="game-title"]').textContent();
26-
expect(gameTitle?.trim()).toBeTruthy();
24+
await expect(gameCards.first().getByTestId('game-title')).not.toBeEmpty();
2725
});
2826

2927
test('should navigate to correct game details page when clicking on a game', async ({ page }) => {
3028
await page.goto('/');
3129

3230
// Wait for games to load
33-
await page.waitForSelector('[data-testid="games-grid"]', { timeout: 10000 });
31+
const gamesGrid = page.getByTestId('games-grid');
32+
await expect(gamesGrid).toBeVisible();
3433

3534
// Get the first game card and its data attributes
36-
const firstGameCard = page.locator('[data-testid="game-card"]').first();
35+
const firstGameCard = page.getByTestId('game-card').first();
3736
const gameId = await firstGameCard.getAttribute('data-game-id');
3837
const gameTitle = await firstGameCard.getAttribute('data-game-title');
3938

@@ -44,58 +43,55 @@ test.describe('Game Listing and Navigation', () => {
4443
await expect(page).toHaveURL(`/game/${gameId}`);
4544

4645
// Verify the game details page loads
47-
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
46+
await expect(page.getByTestId('game-details')).toBeVisible();
4847

4948
// Verify the title matches what we clicked on
50-
const detailsTitle = page.locator('[data-testid="game-details-title"]');
51-
await expect(detailsTitle).toHaveText(gameTitle || '');
49+
if (gameTitle) {
50+
await expect(page.getByTestId('game-details-title')).toHaveText(gameTitle);
51+
}
5252
});
5353

5454
test('should display game details with all required information', async ({ page }) => {
5555
// Navigate to a specific game (we'll use game ID 1 as an example)
5656
await page.goto('/game/1');
5757

5858
// Wait for game details to load
59-
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
59+
await expect(page.getByTestId('game-details')).toBeVisible();
6060

6161
// Check that the game title is present and not empty
62-
const gameTitle = page.locator('[data-testid="game-details-title"]');
62+
const gameTitle = page.getByTestId('game-details-title');
6363
await expect(gameTitle).toBeVisible();
64-
const titleText = await gameTitle.textContent();
65-
expect(titleText?.trim()).toBeTruthy();
64+
await expect(gameTitle).not.toBeEmpty();
6665

6766
// Check that the game description is present and not empty
68-
const gameDescription = page.locator('[data-testid="game-details-description"]');
67+
const gameDescription = page.getByTestId('game-details-description');
6968
await expect(gameDescription).toBeVisible();
70-
const descriptionText = await gameDescription.textContent();
71-
expect(descriptionText?.trim()).toBeTruthy();
69+
await expect(gameDescription).not.toBeEmpty();
7270

7371
// Check that either publisher or category (or both) are present
74-
const publisherExists = await page.locator('[data-testid="game-details-publisher"]').isVisible();
75-
const categoryExists = await page.locator('[data-testid="game-details-category"]').isVisible();
76-
expect(publisherExists && categoryExists).toBeTruthy();
72+
const publisherExists = await page.getByTestId('game-details-publisher').isVisible();
73+
const categoryExists = await page.getByTestId('game-details-category').isVisible();
74+
expect(publisherExists || categoryExists).toBeTruthy();
7775

7876
// If publisher exists, check it has content
7977
if (publisherExists) {
80-
const publisherText = await page.locator('[data-testid="game-details-publisher"]').textContent();
81-
expect(publisherText?.trim()).toBeTruthy();
78+
await expect(page.getByTestId('game-details-publisher')).not.toBeEmpty();
8279
}
8380

8481
// If category exists, check it has content
8582
if (categoryExists) {
86-
const categoryText = await page.locator('[data-testid="game-details-category"]').textContent();
87-
expect(categoryText?.trim()).toBeTruthy();
83+
await expect(page.getByTestId('game-details-category')).not.toBeEmpty();
8884
}
8985
});
9086

9187
test('should display a button to back the game', async ({ page }) => {
9288
await page.goto('/game/1');
9389

9490
// Wait for game details to load
95-
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
91+
await expect(page.getByTestId('game-details')).toBeVisible();
9692

9793
// Check that the back game button is present
98-
const backButton = page.locator('[data-testid="back-game-button"]');
94+
const backButton = page.getByTestId('back-game-button');
9995
await expect(backButton).toBeVisible();
10096
await expect(backButton).toContainText('Support This Game');
10197

@@ -107,25 +103,24 @@ test.describe('Game Listing and Navigation', () => {
107103
await page.goto('/game/1');
108104

109105
// Wait for the page to load
110-
await page.waitForSelector('[data-testid="game-details"]', { timeout: 10000 });
106+
await expect(page.getByTestId('game-details')).toBeVisible();
111107

112-
// Find and click the back to all games link
113-
const backLink = page.locator('a:has-text("Back to all games")');
108+
// Find and click the back to all games link using a more semantic locator
109+
const backLink = page.getByRole('link', { name: /back to all games/i });
114110
await expect(backLink).toBeVisible();
115111
await backLink.click();
116112

117113
// Verify we're back on the home page
118114
await expect(page).toHaveURL('/');
119-
await page.waitForSelector('[data-testid="games-grid"]', { timeout: 10000 });
115+
await expect(page.getByTestId('games-grid')).toBeVisible();
120116
});
121117

122118
test('should handle navigation to non-existent game gracefully', async ({ page }) => {
123119
// Navigate to a game that doesn't exist
124-
await page.goto('/game/99999');
120+
const response = await page.goto('/game/99999');
125121

126-
// The page should load without crashing
127-
// Check if there's an error message or if it handles gracefully
128-
await page.waitForTimeout(3000);
122+
// Check the response status or ensure the page loads without crashing
123+
expect(response?.status()).toBeLessThan(500);
129124

130125
// The page should either show an error or handle it gracefully
131126
// We expect the page to not crash and still have a valid title

client/e2e-tests/home.spec.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
import { test, expect } from '@playwright/test';
22

33
test.describe('Home Page', () => {
4-
test('should display the correct title', async ({ page }) => {
4+
test.beforeEach(async ({ page }) => {
55
await page.goto('/');
6-
6+
});
7+
8+
test('should display the correct title', async ({ page }) => {
79
// Check that the page title is correct
810
await expect(page).toHaveTitle('Tailspin Toys - Crowdfunding your new favorite game!');
911
});
1012

1113
test('should display the main heading', async ({ page }) => {
12-
await page.goto('/');
13-
14-
// Check that the main heading is present
15-
const mainHeading = page.locator('h1').first();
16-
await expect(mainHeading).toHaveText('Tailspin Toys');
14+
// Check that the main page heading is present
15+
await expect(page.getByRole('heading', { name: 'Welcome to Tailspin Toys', exact: true })).toBeVisible();
16+
});
17+
18+
test('should display the site branding in header', async ({ page }) => {
19+
// Check that the site branding is present in the header (no longer an h1)
20+
await expect(page.getByText('Tailspin Toys').first()).toBeVisible();
1721
});
1822

1923
test('should display the welcome message', async ({ page }) => {
20-
await page.goto('/');
21-
22-
// Check that the welcome message is present
23-
const welcomeMessage = page.locator('p').first();
24-
await expect(welcomeMessage).toHaveText('Find your next game! And maybe even back one! Explore our collection!');
24+
// Check that the welcome message is present using more specific locator
25+
await expect(page.getByText('Find your next game! And maybe even back one! Explore our collection!')).toBeVisible();
2526
});
2627
});

0 commit comments

Comments
 (0)