Merge origin/main into feature/shared-packages

This commit is contained in:
Kacper
2025-12-20 01:06:05 +01:00
17 changed files with 11254 additions and 11941 deletions

View File

@@ -110,8 +110,8 @@ test.describe("Kanban Responsive Scaling Tests", () => {
expect(Math.abs(columnWidth - baseWidth)).toBeLessThan(2);
}
// Column width should be within expected bounds (240px min, 360px max)
expect(baseWidth).toBeGreaterThanOrEqual(240);
// Column width should be within expected bounds (280px min, 360px max)
expect(baseWidth).toBeGreaterThanOrEqual(280);
expect(baseWidth).toBeLessThanOrEqual(360);
// Columns should not overlap (check x positions)
@@ -149,9 +149,30 @@ test.describe("Kanban Responsive Scaling Tests", () => {
expect(verifiedBox).not.toBeNull();
if (backlogBox && verifiedBox) {
// Calculate the left and right margins
const leftMargin = backlogBox.x;
const rightMargin = 1600 - (verifiedBox.x + verifiedBox.width);
// Get the actual container width (accounting for sidebar)
// The board-view container is inside a flex container that accounts for sidebar
const containerWidth = await page.evaluate(() => {
const boardView = document.querySelector('[data-testid="board-view"]');
if (!boardView) return window.innerWidth;
const parent = boardView.parentElement;
return parent ? parent.clientWidth : window.innerWidth;
});
// Calculate the left and right margins relative to the container
// The bounding box x is relative to the viewport, so we need to find where
// the container starts relative to the viewport
const containerLeft = await page.evaluate(() => {
const boardView = document.querySelector('[data-testid="board-view"]');
if (!boardView) return 0;
const parent = boardView.parentElement;
if (!parent) return 0;
const rect = parent.getBoundingClientRect();
return rect.left;
});
// Calculate margins relative to the container
const leftMargin = backlogBox.x - containerLeft;
const rightMargin = containerWidth - (verifiedBox.x + verifiedBox.width - containerLeft);
// The margins should be roughly equal (columns are centered)
// Allow for some tolerance due to padding and gaps

View File

@@ -1,184 +0,0 @@
/**
* Settings Marketing Content Toggle Tests
*
* Tests for the "Hide marketing content" setting in the Appearance section.
*/
import { test, expect } from "@playwright/test";
import * as fs from "fs";
import {
waitForNetworkIdle,
createTestGitRepo,
cleanupTempDir,
createTempDirPath,
setupProjectWithPathNoWorktrees,
navigateToSettings,
} from "./utils";
// Create unique temp dir for this test run
const TEST_TEMP_DIR = createTempDirPath("settings-marketing-tests");
interface TestRepo {
path: string;
cleanup: () => Promise<void>;
}
// Configure all tests to run serially
test.describe.configure({ mode: "serial" });
test.describe("Settings Marketing Content Tests", () => {
let testRepo: TestRepo;
test.beforeAll(async () => {
// Create test temp directory
if (!fs.existsSync(TEST_TEMP_DIR)) {
fs.mkdirSync(TEST_TEMP_DIR, { recursive: true });
}
});
test.beforeEach(async () => {
// Create a fresh test repo for each test
testRepo = await createTestGitRepo(TEST_TEMP_DIR);
});
test.afterEach(async () => {
// Cleanup test repo after each test
if (testRepo) {
await testRepo.cleanup();
}
});
test.afterAll(async () => {
// Cleanup temp directory
cleanupTempDir(TEST_TEMP_DIR);
});
test("should show course promo badge by default", async ({ page }) => {
// Setup project without worktrees for simpler testing
await setupProjectWithPathNoWorktrees(page, testRepo.path);
await page.goto("/");
await waitForNetworkIdle(page);
// Wait for sidebar to load
await expect(page.locator('[data-testid="sidebar"]')).toBeVisible({
timeout: 10000,
});
// Course promo badge should be visible by default
const promoBadge = page.locator('[data-testid="course-promo-badge"]');
await expect(promoBadge).toBeVisible({ timeout: 5000 });
});
test("should hide course promo badge when setting is enabled", async ({
page,
}) => {
// Setup project
await setupProjectWithPathNoWorktrees(page, testRepo.path);
await page.goto("/");
await waitForNetworkIdle(page);
// Navigate to settings
await navigateToSettings(page);
// Click on Appearance tab in settings navigation
const appearanceTab = page.getByRole("button", { name: /appearance/i });
await appearanceTab.click();
// Find and click the hide marketing content checkbox
const hideMarketingCheckbox = page.locator(
'[data-testid="hide-marketing-content-checkbox"]'
);
await expect(hideMarketingCheckbox).toBeVisible({ timeout: 5000 });
await hideMarketingCheckbox.click();
// Navigate back to board to see the sidebar
await page.goto("/board");
await waitForNetworkIdle(page);
// Wait for Zustand store to rehydrate from localStorage
await page.waitForFunction(() => {
const storage = localStorage.getItem('automaker-storage');
if (!storage) return false;
const parsed = JSON.parse(storage);
return parsed.state?.hideMarketingContent === true;
});
// Course promo badge should now be hidden
const promoBadge = page.locator('[data-testid="course-promo-badge"]');
await expect(promoBadge).not.toBeVisible({ timeout: 5000 });
});
test("should persist hide marketing setting across page reloads", async ({
page,
}) => {
// Setup project
await setupProjectWithPathNoWorktrees(page, testRepo.path);
await page.goto("/");
await waitForNetworkIdle(page);
// Navigate to settings and enable hide marketing
await navigateToSettings(page);
const appearanceTab = page.getByRole("button", { name: /appearance/i });
await appearanceTab.click();
const hideMarketingCheckbox = page.locator(
'[data-testid="hide-marketing-content-checkbox"]'
);
await hideMarketingCheckbox.click();
// Reload the page
await page.reload();
await waitForNetworkIdle(page);
// Course promo badge should still be hidden after reload
const promoBadge = page.locator('[data-testid="course-promo-badge"]');
await expect(promoBadge).not.toBeVisible({ timeout: 5000 });
});
test("should show course promo badge again when setting is disabled", async ({
page,
}) => {
// Setup project with hide marketing already enabled via localStorage
await page.addInitScript(() => {
const state = {
state: {
hideMarketingContent: true,
projects: [],
currentProject: null,
theme: "dark",
sidebarOpen: true,
},
version: 2,
};
localStorage.setItem("automaker-storage", JSON.stringify(state));
});
await setupProjectWithPathNoWorktrees(page, testRepo.path);
await page.goto("/");
await waitForNetworkIdle(page);
// Verify promo is hidden initially
const promoBadge = page.locator('[data-testid="course-promo-badge"]');
await expect(promoBadge).not.toBeVisible({ timeout: 5000 });
// Navigate to settings and disable hide marketing
await navigateToSettings(page);
const appearanceTab = page.getByRole("button", { name: /appearance/i });
await appearanceTab.click();
const hideMarketingCheckbox = page.locator(
'[data-testid="hide-marketing-content-checkbox"]'
);
await hideMarketingCheckbox.click(); // Uncheck
// Navigate back to board
await page.goto("/board");
await waitForNetworkIdle(page);
// Course promo badge should now be visible again
await expect(promoBadge).toBeVisible({ timeout: 5000 });
});
});

View File

@@ -799,10 +799,14 @@ test.describe("Worktree Integration Tests", () => {
await clickAddFeature(page);
// Fill in the feature details with the new branch
await fillAddFeatureDialog(page, "Feature that should auto-create worktree", {
branch: branchName,
category: "Testing",
});
await fillAddFeatureDialog(
page,
"Feature that should auto-create worktree",
{
branch: branchName,
category: "Testing",
}
);
// Confirm
await confirmAddFeature(page);
@@ -835,7 +839,7 @@ test.describe("Worktree Integration Tests", () => {
const featureFilePath = path.join(featuresDir, featureDir!, "feature.json");
const featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
// Verify branch name is stored
expect(featureData.branchName).toBe(branchName);
@@ -900,7 +904,7 @@ test.describe("Worktree Integration Tests", () => {
let featureFilePath = path.join(featuresDir, featureDir!, "feature.json");
let featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
// Verify feature was created with the branch name stored
expect(featureData.branchName).toBe(branchName);
// Verify worktreePath is NOT set (worktrees are created at execution time, not when adding)
@@ -1084,7 +1088,9 @@ test.describe("Worktree Integration Tests", () => {
// When a worktree is selected, "Use current selected branch" should be selected
// and the branch name should be shown in the label
const currentBranchLabel = page.locator('label[for="feature-current"]');
await expect(currentBranchLabel).toContainText(branchName, { timeout: 5000 });
await expect(currentBranchLabel).toContainText(branchName, {
timeout: 5000,
});
// Close dialog
await page.keyboard.press("Escape");
@@ -1275,11 +1281,7 @@ test.describe("Worktree Integration Tests", () => {
expect(featureDir).toBeDefined();
// Read the feature data
const featureFilePath = path.join(
featuresDir,
featureDir!,
"feature.json"
);
const featureFilePath = path.join(featuresDir, featureDir!, "feature.json");
const featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
expect(featureData.status).toBe("backlog");
@@ -1296,9 +1298,7 @@ test.describe("Worktree Integration Tests", () => {
// Wait for the feature to move to in_progress column
await expect(async () => {
const updatedData = JSON.parse(
fs.readFileSync(featureFilePath, "utf-8")
);
const updatedData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
expect(updatedData.status).toBe("in_progress");
}).toPass({ timeout: 10000 });
@@ -1911,7 +1911,10 @@ test.describe("Worktree Integration Tests", () => {
await apiCreateWorktree(page, testRepo.path, branchName);
// Add a file and commit in the worktree
fs.writeFileSync(path.join(worktreePath, "merge-file.txt"), "merge content");
fs.writeFileSync(
path.join(worktreePath, "merge-file.txt"),
"merge content"
);
await execAsync("git add merge-file.txt", { cwd: worktreePath });
await execAsync('git commit -m "Add file for merge test"', {
cwd: worktreePath,
@@ -2065,9 +2068,9 @@ test.describe("Worktree Integration Tests", () => {
// Verify the worktree has the file from develop
const worktreePath = getWorktreePath(testRepo.path, "feature/from-develop");
expect(
fs.existsSync(path.join(worktreePath, "develop-only.txt"))
).toBe(true);
expect(fs.existsSync(path.join(worktreePath, "develop-only.txt"))).toBe(
true
);
const content = fs.readFileSync(
path.join(worktreePath, "develop-only.txt"),
"utf-8"
@@ -2100,10 +2103,9 @@ test.describe("Worktree Integration Tests", () => {
// Verify the worktree starts from the same commit as main
const worktreePath = getWorktreePath(testRepo.path, "feature/from-head");
const { stdout: worktreeHash } = await execAsync(
"git rev-parse HEAD~0",
{ cwd: worktreePath }
);
const { stdout: worktreeHash } = await execAsync("git rev-parse HEAD~0", {
cwd: worktreePath,
});
// The worktree's initial commit should be the same as main's HEAD
// (Since it was just created, we check the parent commit)
@@ -2395,9 +2397,9 @@ test.describe("Worktree Integration Tests", () => {
let featureData = JSON.parse(fs.readFileSync(featureFilePath, "utf-8"));
// Initially, the feature should be on main or have no branch set
expect(
!featureData.branchName || featureData.branchName === "main"
).toBe(true);
expect(!featureData.branchName || featureData.branchName === "main").toBe(
true
);
// The new branch we want to assign
const newBranchName = "feature/edited-branch";
@@ -2428,7 +2430,7 @@ test.describe("Worktree Integration Tests", () => {
await page.waitForTimeout(300);
// Type the new branch name
const commandInput = page.locator('[cmdk-input]');
const commandInput = page.locator("[cmdk-input]");
await commandInput.fill(newBranchName);
// Press Enter to select/create the branch
@@ -2519,7 +2521,7 @@ test.describe("Worktree Integration Tests", () => {
await page.waitForTimeout(300);
// Type "main" to change to main branch
const commandInput = page.locator('[cmdk-input]');
const commandInput = page.locator("[cmdk-input]");
await commandInput.fill("main");
await commandInput.press("Enter");
await page.waitForTimeout(200);
@@ -2577,7 +2579,7 @@ test.describe("Worktree Integration Tests", () => {
await branchInput.click();
await page.waitForTimeout(300);
const commandInput = page.locator('[cmdk-input]');
const commandInput = page.locator("[cmdk-input]");
await commandInput.fill(existingBranch);
await commandInput.press("Enter");
await page.waitForTimeout(200);