experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(components): expand coverage to 56 tests with deterministic waits

- Add tests for carousel, grid, popover, drawer, tooltip
- Add component combo tests (cards in grid, dialog with form, tooltip on disabled)
- Add test fixtures to test-page.html for new components
- Replace all waitForTimeout with waitForFunction for reliable state checks

+408 -5
+331 -5
tests/components/components.spec.ts
··· 301 301 wrapper?.click(); 302 302 }); 303 303 304 - await page.waitForTimeout(50); 304 + // Wait for checked state to change 305 + await page.waitForFunction(() => { 306 + const el = document.querySelector('#switch-off') as any; 307 + return el?.checked === true; 308 + }); 305 309 306 310 const newState = await page.evaluate(() => { 307 311 const el = document.querySelector('#switch-off') as any; ··· 325 329 test.describe('peek-dialog', () => { 326 330 test('opens when show() is called', async () => { 327 331 await page.click('#open-dialog'); 328 - await page.waitForTimeout(100); 332 + 333 + // Wait for dialog to open 334 + await page.waitForFunction(() => { 335 + const el = document.querySelector('#test-dialog') as any; 336 + return el?.open === true; 337 + }); 329 338 330 339 const isOpen = await page.evaluate(() => { 331 340 const el = document.querySelector('#test-dialog') as any; ··· 335 344 336 345 // Close for next tests 337 346 await page.click('#close-dialog'); 338 - await page.waitForTimeout(100); 347 + await page.waitForFunction(() => { 348 + const el = document.querySelector('#test-dialog') as any; 349 + return el?.open === false; 350 + }); 339 351 }); 340 352 341 353 test('closes when close() is called', async () => { 342 354 await page.click('#open-dialog'); 343 - await page.waitForTimeout(100); 355 + await page.waitForFunction(() => { 356 + const el = document.querySelector('#test-dialog') as any; 357 + return el?.open === true; 358 + }); 359 + 344 360 await page.click('#close-dialog'); 345 - await page.waitForTimeout(100); 361 + await page.waitForFunction(() => { 362 + const el = document.querySelector('#test-dialog') as any; 363 + return el?.open === false; 364 + }); 346 365 347 366 const isOpen = await page.evaluate(() => { 348 367 const el = document.querySelector('#test-dialog') as any; ··· 431 450 return el?.value; 432 451 }); 433 452 expect(value).toBe('opt2'); 453 + }); 454 + }); 455 + 456 + // ========================================================================== 457 + // peek-carousel 458 + // ========================================================================== 459 + 460 + test.describe('peek-carousel', () => { 461 + test('renders slides', async () => { 462 + const slideCount = await page.evaluate(() => { 463 + const carousel = document.querySelector('#carousel-basic') as any; 464 + return carousel?.items?.length; 465 + }); 466 + expect(slideCount).toBe(3); 467 + }); 468 + 469 + test('starts at first slide', async () => { 470 + const activeIndex = await page.evaluate(() => { 471 + const carousel = document.querySelector('#carousel-basic') as any; 472 + return carousel?.activeIndex; 473 + }); 474 + expect(activeIndex).toBe(0); 475 + }); 476 + 477 + test('has controls when enabled', async () => { 478 + const hasControls = await page.evaluate(() => { 479 + const carousel = document.querySelector('#carousel-basic'); 480 + const prev = carousel?.shadowRoot?.querySelector('[part="prev"]'); 481 + const next = carousel?.shadowRoot?.querySelector('[part="next"]'); 482 + return !!prev && !!next; 483 + }); 484 + expect(hasControls).toBe(true); 485 + }); 486 + 487 + test('has indicators when enabled', async () => { 488 + const indicatorCount = await page.evaluate(() => { 489 + const carousel = document.querySelector('#carousel-basic'); 490 + const indicators = carousel?.shadowRoot?.querySelectorAll('.indicator'); 491 + return indicators?.length; 492 + }); 493 + expect(indicatorCount).toBe(3); 494 + }); 495 + 496 + test('has navigation methods', async () => { 497 + const hasMethods = await page.evaluate(() => { 498 + const carousel = document.querySelector('#carousel-basic') as any; 499 + return { 500 + hasNext: typeof carousel?.next === 'function', 501 + hasPrev: typeof carousel?.prev === 'function', 502 + hasGoTo: typeof carousel?.goTo === 'function' 503 + }; 504 + }); 505 + expect(hasMethods.hasNext).toBe(true); 506 + expect(hasMethods.hasPrev).toBe(true); 507 + expect(hasMethods.hasGoTo).toBe(true); 508 + }); 509 + 510 + test('loop carousel wraps at end', async () => { 511 + const canWrap = await page.evaluate(() => { 512 + const carousel = document.querySelector('#carousel-loop') as any; 513 + return carousel?.loop === true; 514 + }); 515 + expect(canWrap).toBe(true); 516 + }); 517 + }); 518 + 519 + // ========================================================================== 520 + // peek-grid 521 + // ========================================================================== 522 + 523 + test.describe('peek-grid', () => { 524 + test('renders grid container', async () => { 525 + const hasGrid = await page.evaluate(() => { 526 + const grid = document.querySelector('#grid-basic'); 527 + const inner = grid?.shadowRoot?.querySelector('[part="grid"]'); 528 + return !!inner; 529 + }); 530 + expect(hasGrid).toBe(true); 531 + }); 532 + 533 + test('uses auto-fit by default', async () => { 534 + const gridColumns = await page.evaluate(() => { 535 + const grid = document.querySelector('#grid-basic'); 536 + const inner = grid?.shadowRoot?.querySelector('.grid') as HTMLElement; 537 + return inner?.style.getPropertyValue('--_grid-columns'); 538 + }); 539 + expect(gridColumns).toContain('auto-fit'); 540 + }); 541 + 542 + test('respects fixed columns', async () => { 543 + const gridColumns = await page.evaluate(() => { 544 + const grid = document.querySelector('#grid-fixed'); 545 + const inner = grid?.shadowRoot?.querySelector('.grid') as HTMLElement; 546 + return inner?.style.getPropertyValue('--_grid-columns'); 547 + }); 548 + expect(gridColumns).toContain('repeat(2'); 549 + }); 550 + 551 + test('applies gap', async () => { 552 + const gap = await page.evaluate(() => { 553 + const grid = document.querySelector('#grid-basic'); 554 + const inner = grid?.shadowRoot?.querySelector('.grid') as HTMLElement; 555 + return inner?.style.getPropertyValue('--_grid-gap'); 556 + }); 557 + expect(gap).toBe('8px'); 558 + }); 559 + }); 560 + 561 + // ========================================================================== 562 + // peek-popover 563 + // ========================================================================== 564 + 565 + test.describe('peek-popover', () => { 566 + test('closed by default', async () => { 567 + const isOpen = await page.evaluate(() => { 568 + const popover = document.querySelector('#popover-basic') as any; 569 + return popover?.open; 570 + }); 571 + expect(isOpen).toBe(false); 572 + }); 573 + 574 + test('has trigger slot', async () => { 575 + const hasTrigger = await page.evaluate(() => { 576 + const popover = document.querySelector('#popover-basic'); 577 + const slot = popover?.shadowRoot?.querySelector('slot[name="trigger"]'); 578 + return !!slot; 579 + }); 580 + expect(hasTrigger).toBe(true); 581 + }); 582 + 583 + test('has popover element', async () => { 584 + const hasPopover = await page.evaluate(() => { 585 + const popover = document.querySelector('#popover-basic'); 586 + const el = popover?.shadowRoot?.querySelector('[popover]'); 587 + return !!el; 588 + }); 589 + expect(hasPopover).toBe(true); 590 + }); 591 + }); 592 + 593 + // ========================================================================== 594 + // peek-drawer 595 + // ========================================================================== 596 + 597 + test.describe('peek-drawer', () => { 598 + test('closed by default', async () => { 599 + const isOpen = await page.evaluate(() => { 600 + const drawer = document.querySelector('#test-drawer') as any; 601 + return drawer?.open; 602 + }); 603 + expect(isOpen).toBe(false); 604 + }); 605 + 606 + test('uses native dialog element', async () => { 607 + const tagName = await page.evaluate(() => { 608 + const drawer = document.querySelector('#test-drawer'); 609 + const dialog = drawer?.shadowRoot?.querySelector('dialog'); 610 + return dialog?.tagName.toLowerCase(); 611 + }); 612 + expect(tagName).toBe('dialog'); 613 + }); 614 + 615 + test('opens when show() is called', async () => { 616 + await page.click('#open-drawer'); 617 + 618 + // Wait for drawer to open 619 + await page.waitForFunction(() => { 620 + const drawer = document.querySelector('#test-drawer') as any; 621 + return drawer?.open === true; 622 + }); 623 + 624 + const isOpen = await page.evaluate(() => { 625 + const drawer = document.querySelector('#test-drawer') as any; 626 + return drawer?.open; 627 + }); 628 + expect(isOpen).toBe(true); 629 + 630 + // Close drawer 631 + await page.evaluate(() => { 632 + const drawer = document.querySelector('#test-drawer') as any; 633 + drawer?.close(); 634 + }); 635 + await page.waitForFunction(() => { 636 + const drawer = document.querySelector('#test-drawer') as any; 637 + return drawer?.open === false; 638 + }); 639 + }); 640 + 641 + test('has close button', async () => { 642 + const hasClose = await page.evaluate(() => { 643 + const drawer = document.querySelector('#test-drawer'); 644 + const btn = drawer?.shadowRoot?.querySelector('.close-btn'); 645 + return !!btn; 646 + }); 647 + expect(hasClose).toBe(true); 648 + }); 649 + }); 650 + 651 + // ========================================================================== 652 + // peek-tooltip 653 + // ========================================================================== 654 + 655 + test.describe('peek-tooltip', () => { 656 + test('has content attribute', async () => { 657 + const content = await page.evaluate(() => { 658 + const tooltip = document.querySelector('#tooltip-top') as any; 659 + return tooltip?.content; 660 + }); 661 + expect(content).toBe('Tooltip on top'); 662 + }); 663 + 664 + test('uses popover manual mode', async () => { 665 + const popoverMode = await page.evaluate(() => { 666 + const tooltip = document.querySelector('#tooltip-top'); 667 + const el = tooltip?.shadowRoot?.querySelector('.tooltip'); 668 + return el?.getAttribute('popover'); 669 + }); 670 + expect(popoverMode).toBe('manual'); 671 + }); 672 + 673 + test('has role tooltip', async () => { 674 + const role = await page.evaluate(() => { 675 + const tooltip = document.querySelector('#tooltip-top'); 676 + const el = tooltip?.shadowRoot?.querySelector('.tooltip'); 677 + return el?.getAttribute('role'); 678 + }); 679 + expect(role).toBe('tooltip'); 680 + }); 681 + 682 + test('positions can be set', async () => { 683 + const position = await page.evaluate(() => { 684 + const tooltip = document.querySelector('#tooltip-bottom') as any; 685 + return tooltip?.position; 686 + }); 687 + expect(position).toBe('bottom'); 688 + }); 689 + }); 690 + 691 + // ========================================================================== 692 + // Component Combos 693 + // ========================================================================== 694 + 695 + test.describe('Component Combos', () => { 696 + test('cards render in grid', async () => { 697 + const cardsInGrid = await page.evaluate(() => { 698 + const grid = document.querySelector('#combo-cards-grid'); 699 + const cards = grid?.querySelectorAll('peek-card'); 700 + return cards?.length; 701 + }); 702 + expect(cardsInGrid).toBe(2); 703 + }); 704 + 705 + test('dialog contains form elements', async () => { 706 + await page.click('#open-form-dialog'); 707 + 708 + // Wait for dialog to open 709 + await page.waitForFunction(() => { 710 + const dialog = document.querySelector('#form-dialog') as any; 711 + return dialog?.open === true; 712 + }); 713 + 714 + const hasFormElements = await page.evaluate(() => { 715 + const dialog = document.querySelector('#form-dialog'); 716 + const input = dialog?.querySelector('peek-input'); 717 + const switchEl = dialog?.querySelector('peek-switch'); 718 + return !!input && !!switchEl; 719 + }); 720 + expect(hasFormElements).toBe(true); 721 + 722 + // Close dialog 723 + await page.click('#form-dialog-cancel'); 724 + await page.waitForFunction(() => { 725 + const dialog = document.querySelector('#form-dialog') as any; 726 + return dialog?.open === false; 727 + }); 728 + }); 729 + 730 + test('tooltip works on disabled button', async () => { 731 + const tooltipContent = await page.evaluate(() => { 732 + const tooltip = document.querySelector('#tooltip-disabled') as any; 733 + return tooltip?.content; 734 + }); 735 + expect(tooltipContent).toBe('This button is disabled'); 736 + }); 737 + 738 + test('nested buttons in dialog footer are accessible', async () => { 739 + await page.click('#open-form-dialog'); 740 + 741 + // Wait for dialog to open 742 + await page.waitForFunction(() => { 743 + const dialog = document.querySelector('#form-dialog') as any; 744 + return dialog?.open === true; 745 + }); 746 + 747 + const buttons = await page.evaluate(() => { 748 + const dialog = document.querySelector('#form-dialog'); 749 + const footer = dialog?.querySelector('[slot="footer"]'); 750 + const btns = footer?.querySelectorAll('peek-button'); 751 + return btns?.length; 752 + }); 753 + expect(buttons).toBe(2); 754 + 755 + await page.click('#form-dialog-cancel'); 756 + await page.waitForFunction(() => { 757 + const dialog = document.querySelector('#form-dialog') as any; 758 + return dialog?.open === false; 759 + }); 434 760 }); 435 761 }); 436 762
+77
tests/components/test-page.html
··· 222 222 </peek-drawer> 223 223 </section> 224 224 225 + <!-- Carousel Tests --> 226 + <section class="test-section" id="carousel-tests"> 227 + <h2>peek-carousel</h2> 228 + <peek-carousel id="carousel-basic" controls indicators style="width: 300px;"> 229 + <div style="width: 100%; height: 150px; background: #e0e0e0; display: flex; align-items: center; justify-content: center;">Slide 1</div> 230 + <div style="width: 100%; height: 150px; background: #d0d0d0; display: flex; align-items: center; justify-content: center;">Slide 2</div> 231 + <div style="width: 100%; height: 150px; background: #c0c0c0; display: flex; align-items: center; justify-content: center;">Slide 3</div> 232 + </peek-carousel> 233 + <peek-carousel id="carousel-loop" controls loop style="width: 300px; margin-top: 16px;"> 234 + <div style="width: 100%; height: 100px; background: #e8e8e8;">Loop 1</div> 235 + <div style="width: 100%; height: 100px; background: #d8d8d8;">Loop 2</div> 236 + </peek-carousel> 237 + </section> 238 + 239 + <!-- Grid Tests --> 240 + <section class="test-section" id="grid-tests"> 241 + <h2>peek-grid</h2> 242 + <peek-grid id="grid-basic" min-item-width="100" gap="8"> 243 + <div style="background: #e0e0e0; padding: 16px;">Item 1</div> 244 + <div style="background: #d0d0d0; padding: 16px;">Item 2</div> 245 + <div style="background: #c0c0c0; padding: 16px;">Item 3</div> 246 + <div style="background: #b0b0b0; padding: 16px;">Item 4</div> 247 + </peek-grid> 248 + <peek-grid id="grid-fixed" columns="2" gap="12" style="margin-top: 16px;"> 249 + <div style="background: #e8e8e8; padding: 16px;">Fixed 1</div> 250 + <div style="background: #d8d8d8; padding: 16px;">Fixed 2</div> 251 + </peek-grid> 252 + </section> 253 + 254 + <!-- Popover Tests --> 255 + <section class="test-section" id="popover-tests"> 256 + <h2>peek-popover</h2> 257 + <peek-popover id="popover-basic" position="bottom"> 258 + <peek-button slot="trigger">Open Popover</peek-button> 259 + <div>Popover content here</div> 260 + </peek-popover> 261 + </section> 262 + 263 + <!-- Component Combos --> 264 + <section class="test-section" id="combo-tests"> 265 + <h2>Component Combos</h2> 266 + 267 + <!-- Cards in Grid --> 268 + <peek-grid id="combo-cards-grid" min-item-width="150" gap="12"> 269 + <peek-card id="combo-card-1"><p>Grid Card 1</p></peek-card> 270 + <peek-card id="combo-card-2"><p>Grid Card 2</p></peek-card> 271 + </peek-grid> 272 + 273 + <!-- Dialog with Form --> 274 + <peek-button id="open-form-dialog">Open Form Dialog</peek-button> 275 + <peek-dialog id="form-dialog"> 276 + <span slot="header">Form Dialog</span> 277 + <peek-input id="dialog-input" placeholder="Enter name..."></peek-input> 278 + <peek-switch id="dialog-switch">Enable feature</peek-switch> 279 + <div slot="footer"> 280 + <peek-button id="form-dialog-cancel" variant="ghost">Cancel</peek-button> 281 + <peek-button id="form-dialog-submit" variant="primary">Submit</peek-button> 282 + </div> 283 + </peek-dialog> 284 + 285 + <!-- Tooltip on Disabled --> 286 + <peek-tooltip id="tooltip-disabled" content="This button is disabled" position="top"> 287 + <peek-button id="btn-with-tooltip" disabled>Disabled with Tooltip</peek-button> 288 + </peek-tooltip> 289 + </section> 290 + 225 291 <script type="module"> 226 292 // Import all components 227 293 import '/app/components/index.js'; ··· 259 325 // Drawer handler 260 326 document.getElementById('open-drawer').addEventListener('click', () => { 261 327 document.getElementById('test-drawer').show(); 328 + }); 329 + 330 + // Form dialog handlers 331 + document.getElementById('open-form-dialog').addEventListener('click', () => { 332 + document.getElementById('form-dialog').show(); 333 + }); 334 + document.getElementById('form-dialog-cancel').addEventListener('click', () => { 335 + document.getElementById('form-dialog').close(); 336 + }); 337 + document.getElementById('form-dialog-submit').addEventListener('click', () => { 338 + document.getElementById('form-dialog').close(); 262 339 }); 263 340 264 341 // Signal ready