Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
<h2 class="page-title">All Contacts</h2>
<div class="contact-list-container">
<for each="contact in contacts">
<cb-contact-card id="{{contact.id}}" first-name="{{contact.firstName}}" last-name="{{contact.lastName}}"
email="{{contact.email}}" phone="{{contact.phone}}" company="{{contact.company}}" group="{{contact.group}}"
favorite="{{contact.favorite}}" initials="{{contact.initials}}" avatar-color="{{contact.avatarColor}}"
notes="{{contact.notes}}" address="{{contact.address}}"></cb-contact-card>
</for>
</div>
<if condition="contacts.length">

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.

This repeats the contact-card loop and empty-state behavior that already exists in cb-contact-list, and the same duplication is added to favorites/group. Can we reuse that component instead?

<h2 class="page-title">All Contacts</h2>
<cb-contact-list :contacts="{{contacts}}"></cb-contact-list>

Then the page TS only needs to import the list component:

import '#organisms/cb-contact-list/cb-contact-list.js';

If the page-specific container class is needed for styling, let's move/parameterize that in the shared list component rather than duplicating the loop in three pages.

<div class="contact-list-container">
<for each="contact in contacts">
<cb-contact-card id="{{contact.id}}" first-name="{{contact.firstName}}" last-name="{{contact.lastName}}"
email="{{contact.email}}" phone="{{contact.phone}}" company="{{contact.company}}" group="{{contact.group}}"
favorite="{{contact.favorite}}" initials="{{contact.initials}}" avatar-color="{{contact.avatarColor}}"
notes="{{contact.notes}}" address="{{contact.address}}"></cb-contact-card>
</for>
</div>
</if>
<if condition="!contacts.length">
<cb-empty-state title="No contacts found" message="Try adjusting your search or add a new contact."></cb-empty-state>
</if>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.

import { WebUIElement, observable } from '@microsoft/webui-framework';
import '#atoms/cb-empty-state/cb-empty-state.js';
import '#organisms/cb-contact-card/cb-contact-card.js';
import { Contact } from '#api';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
<h2 class="page-title">Favorites</h2>
<div class="contact-list-container">
<for each="contact in contacts">
<cb-contact-card id="{{contact.id}}" first-name="{{contact.firstName}}" last-name="{{contact.lastName}}"
email="{{contact.email}}" phone="{{contact.phone}}" company="{{contact.company}}" group="{{contact.group}}"
favorite="{{contact.favorite}}" initials="{{contact.initials}}" avatar-color="{{contact.avatarColor}}"
notes="{{contact.notes}}" address="{{contact.address}}"></cb-contact-card>
</for>
</div>
<if condition="contacts.length">
<div class="contact-list-container">
<for each="contact in contacts">
<cb-contact-card id="{{contact.id}}" first-name="{{contact.firstName}}" last-name="{{contact.lastName}}"
email="{{contact.email}}" phone="{{contact.phone}}" company="{{contact.company}}" group="{{contact.group}}"
favorite="{{contact.favorite}}" initials="{{contact.initials}}" avatar-color="{{contact.avatarColor}}"
notes="{{contact.notes}}" address="{{contact.address}}"></cb-contact-card>
</for>
</div>
</if>
<if condition="!contacts.length">
<cb-empty-state title="No contacts found" message="Try adjusting your search."></cb-empty-state>
</if>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.

import { WebUIElement, observable } from '@microsoft/webui-framework';
import '#atoms/cb-empty-state/cb-empty-state.js';
import '#organisms/cb-contact-card/cb-contact-card.js';
import { Contact } from '#api';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
<h2 class="page-title">{{groupName}}</h2>
<div class="contact-list-container">
<for each="contact in contacts">
<cb-contact-card id="{{contact.id}}" first-name="{{contact.firstName}}" last-name="{{contact.lastName}}"
email="{{contact.email}}" phone="{{contact.phone}}" company="{{contact.company}}" group="{{contact.group}}"
favorite="{{contact.favorite}}" initials="{{contact.initials}}" avatar-color="{{contact.avatarColor}}"
notes="{{contact.notes}}" address="{{contact.address}}"></cb-contact-card>
</for>
</div>
<if condition="contacts.length">
<div class="contact-list-container">
<for each="contact in contacts">
<cb-contact-card id="{{contact.id}}" first-name="{{contact.firstName}}" last-name="{{contact.lastName}}"
email="{{contact.email}}" phone="{{contact.phone}}" company="{{contact.company}}" group="{{contact.group}}"
favorite="{{contact.favorite}}" initials="{{contact.initials}}" avatar-color="{{contact.avatarColor}}"
notes="{{contact.notes}}" address="{{contact.address}}"></cb-contact-card>
</for>
</div>
</if>
<if condition="!contacts.length">
<cb-empty-state title="No contacts found" message="Try adjusting your search."></cb-empty-state>
</if>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT license.

import { WebUIElement, observable } from '@microsoft/webui-framework';
import '#atoms/cb-empty-state/cb-empty-state.js';
import '#organisms/cb-contact-card/cb-contact-card.js';
import type { Contact } from '#api';

Expand Down
64 changes: 64 additions & 0 deletions examples/app/contact-book-manager/tests/contact-book.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,70 @@ test.describe('contact edit does not corrupt sidebar groups', () => {
});
});

// ── Search tests ────────────────────────────────────────────────

test.describe('search', () => {
test('filters contacts on the contacts page as the user types', async ({ page }) => {
await page.goto('/contacts');
await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(15);

const searchInput = page.locator('cb-header .search-input');
await searchInput.fill('Sarah');

// Only Sarah Chen should remain
await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(1);
await expect(page.locator('cb-page-contacts cb-contact-card')).toContainText('Sarah');
Comment on lines +251 to +261
});

test('shows empty state when search has no matches', async ({ page }) => {
await page.goto('/contacts');
const searchInput = page.locator('cb-header .search-input');
await searchInput.fill('zzz_no_match_zzz');

await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(0);
await expect(page.locator('cb-page-contacts cb-empty-state')).toBeVisible();
});

test('restores full list when search is cleared', async ({ page }) => {
await page.goto('/contacts');
const searchInput = page.locator('cb-header .search-input');
await searchInput.fill('Sarah');
await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(1);

await searchInput.clear();
await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(15);
});

test('filters favorites page as the user types', async ({ page }) => {
await page.goto('/favorites');
const initialCount = await page.locator('cb-page-favorites cb-contact-card').count();
expect(initialCount).toBeGreaterThan(0);

const searchInput = page.locator('cb-header .search-input');
await searchInput.fill('Sarah');

await expect(page.locator('cb-page-favorites cb-contact-card')).toHaveCount(1);
await expect(page.locator('cb-page-favorites cb-contact-card')).toContainText('Sarah');
});

test('search persists when navigating back to contacts page', async ({ page }) => {
await page.goto('/contacts');
const searchInput = page.locator('cb-header .search-input');
await searchInput.fill('Sarah');
await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(1);

// Navigate to a contact detail
await page.locator('cb-page-contacts cb-contact-card').click();
await expect(page.locator('cb-contact-detail')).toBeVisible();

// Navigate back — search box still shows query, contacts page should re-filter
await page.locator('cb-sidebar').getByRole('link', { name: /All Contacts/ }).click();
await expect(page).toHaveURL('/contacts');
await expect(page.locator('cb-header .search-input')).toHaveValue('Sarah');
await expect(page.locator('cb-page-contacts cb-contact-card')).toHaveCount(1);
});
});

// ── Visual regression tests ──────────────────────────────────────

test.describe('visual regression', () => {
Expand Down
Loading