11import { expect , test } from "@playwright/test" ;
22import { MOCK_DB_KEYS } from "../../../src/dev-runtime/persistence/mock-db-store.js" ;
3+ import { getActiveToolRegistry } from "../../../toolbox/toolRegistry.js" ;
34import { isBrowserExtensionNoise } from "../../helpers/browserExtensionNoise.mjs" ;
45import { startRepoServer } from "../../helpers/playwrightRepoServer.mjs" ;
56import { workspaceV2CoverageReporter } from "../../helpers/workspaceV2CoverageReporter.mjs" ;
@@ -13,6 +14,7 @@ const TOOL_ROUTE_SMOKE_CASES = [
1314 { heading : "Saved Data" , route : "/tools/saved-data/index.html" } ,
1415 { heading : "Languages" , route : "/tools/languages/index.html" } ,
1516] ;
17+ const REGISTRY_BY_ID = new Map ( getActiveToolRegistry ( ) . map ( ( tool ) => [ tool . id , tool ] ) ) ;
1618
1719test . afterAll ( async ( ) => {
1820 await workspaceV2CoverageReporter . writeReport ( ) ;
@@ -392,7 +394,7 @@ test("toolbox status kickers, filters, card order, and voting controls work from
392394 }
393395} ) ;
394396
395- test ( "toolbox Build Path status filters show registry-matched tool rows" , async ( { page } ) => {
397+ test ( "toolbox Build Path status filters support multi-select registry-matched tool rows" , async ( { page } ) => {
396398 const server = await startRepoServer ( ) ;
397399 const failedRequests = [ ] ;
398400 const pageErrors = [ ] ;
@@ -418,52 +420,180 @@ test("toolbox Build Path status filters show registry-matched tool rows", async
418420 }
419421 } ) ;
420422
421- async function expectBuildPathChannel ( channel , expectedCount ) {
423+ async function expectActiveFilters ( activeChannels ) {
424+ for ( const releaseChannel of [ "planned" , "wireframe" , "beta" , "complete" ] ) {
425+ const filter = page . locator ( `[data-toolbox-status-filter='${ releaseChannel } ']` ) ;
426+ const active = activeChannels . includes ( releaseChannel ) ;
427+ await expect ( filter ) . toHaveAttribute ( "aria-pressed" , String ( active ) ) ;
428+ if ( active ) {
429+ await expect ( filter ) . toHaveClass ( / p r i m a r y / ) ;
430+ } else {
431+ await expect ( filter ) . not . toHaveClass ( / p r i m a r y / ) ;
432+ }
433+ }
434+ }
435+
436+ async function expectBuildPathChannels ( expectedChannels , expectedCount ) {
422437 await expect ( page . locator ( "[data-build-path-tool]" ) ) . toHaveCount ( expectedCount ) ;
423438 await expect ( page . locator ( "[data-build-path-release-channel]" ) ) . toHaveCount ( expectedCount ) ;
424439 const releaseChannels = await page . locator ( "[data-build-path-release-channel]" ) . evaluateAll ( ( rows ) => (
425440 rows . map ( ( row ) => row . getAttribute ( "data-build-path-release-channel" ) )
426441 ) ) ;
427- expect ( releaseChannels . every ( ( releaseChannel ) => releaseChannel === channel ) ) . toBe ( true ) ;
442+ expect ( releaseChannels . every ( ( releaseChannel ) => expectedChannels . includes ( releaseChannel ) ) ) . toBe ( true ) ;
428443 }
429444
430- async function selectBuildPathFilter ( channel ) {
431- await page . locator ( `[data-toolbox-status-filter='${ channel } ']` ) . click ( ) ;
432- for ( const releaseChannel of [ "planned" , "wireframe" , "beta" , "complete" ] ) {
433- await expect ( page . locator ( `[data-toolbox-status-filter='${ releaseChannel } ']` ) ) . toHaveAttribute (
434- "aria-pressed" ,
435- String ( releaseChannel === channel ) ,
436- ) ;
437- }
445+ async function expectBuildPathOrder ( toolTitle , expectedOrder ) {
446+ await expect ( page . locator ( `[data-build-path-tool='${ toolTitle } '] td` ) . first ( ) ) . toHaveText ( String ( expectedOrder ) ) ;
438447 }
439448
440449 try {
441450 await workspaceV2CoverageReporter . start ( page ) ;
442451 await setServerSession ( server , MOCK_DB_KEYS . users . user1 ) ;
443452 await page . goto ( `${ server . baseUrl } /toolbox/index.html` , { waitUntil : "networkidle" } ) ;
453+ await expect ( page . locator ( "[data-tools-order]" ) ) . toHaveClass ( / p r i m a r y / ) ;
454+ await page . locator ( "[data-tools-sort='grouped']" ) . click ( ) ;
455+ await expect ( page . locator ( "[data-tools-sort='grouped']" ) ) . toHaveClass ( / p r i m a r y / ) ;
444456 await page . locator ( "[data-tools-view='build-path']" ) . click ( ) ;
457+ await expect ( page . locator ( "[data-tools-view='build-path']" ) ) . toHaveClass ( / p r i m a r y / ) ;
458+ await expect ( page . locator ( "[data-tools-order]" ) ) . not . toHaveClass ( / p r i m a r y / ) ;
459+ await expect ( page . locator ( "[data-tools-sort='grouped']" ) ) . not . toHaveClass ( / p r i m a r y / ) ;
445460
446461 await expect ( page . locator ( "[data-toolbox-status-filter]" ) ) . toHaveText ( [
447462 "Planned (28)" ,
448463 "Wireframe (4)" ,
449464 "Beta (5)" ,
450465 "Complete (1)" ,
451466 ] ) ;
452- await expect ( page . locator ( "[data-toolbox-status-filter='planned']" ) ) . toHaveAttribute ( "aria-pressed" , "false" ) ;
453- await expect ( page . locator ( "[data-toolbox-status-filter='wireframe']" ) ) . toHaveAttribute ( "aria-pressed" , "false" ) ;
454- await expect ( page . locator ( "[data-toolbox-status-filter='beta']" ) ) . toHaveAttribute ( "aria-pressed" , "false" ) ;
455- await expect ( page . locator ( "[data-toolbox-status-filter='complete']" ) ) . toHaveAttribute ( "aria-pressed" , "true" ) ;
467+ await expectActiveFilters ( [ "complete" ] ) ;
456468 await expect ( page . locator ( "[data-build-path-tool='Colors']" ) ) . toBeVisible ( ) ;
457- await expectBuildPathChannel ( "complete" , 1 ) ;
469+ await expectBuildPathChannels ( [ "complete" ] , 1 ) ;
470+ await expectBuildPathOrder ( "Colors" , REGISTRY_BY_ID . get ( "colors" ) . order ) ;
471+
472+ await page . locator ( "[data-toolbox-status-filter='planned']" ) . click ( ) ;
473+ await expectActiveFilters ( [ "planned" , "complete" ] ) ;
474+ await expectBuildPathChannels ( [ "planned" , "complete" ] , 29 ) ;
475+ await expect ( page . locator ( "[data-build-path-tool='AI Assistant']" ) ) . toBeVisible ( ) ;
476+ await expectBuildPathOrder ( "AI Assistant" , REGISTRY_BY_ID . get ( "ai-assistant" ) . order ) ;
477+ await expectBuildPathOrder ( "Colors" , REGISTRY_BY_ID . get ( "colors" ) . order ) ;
478+
479+ await page . locator ( "[data-toolbox-status-filter='complete']" ) . click ( ) ;
480+ await expectActiveFilters ( [ "planned" ] ) ;
481+ await expectBuildPathChannels ( [ "planned" ] , 28 ) ;
482+ await expect ( page . locator ( "[data-build-path-tool='Colors']" ) ) . toHaveCount ( 0 ) ;
483+ await expect ( page . locator ( "[data-build-path-tool='AI Assistant']" ) ) . toBeVisible ( ) ;
484+
485+ await page . locator ( "[data-toolbox-status-filter='wireframe']" ) . click ( ) ;
486+ await expectActiveFilters ( [ "planned" , "wireframe" ] ) ;
487+ await expectBuildPathChannels ( [ "planned" , "wireframe" ] , 32 ) ;
488+ await expect ( page . locator ( "[data-build-path-tool='Build Game']" ) ) . toBeVisible ( ) ;
489+ await expectBuildPathOrder ( "Build Game" , REGISTRY_BY_ID . get ( "build-game" ) . order ) ;
490+
491+ await page . locator ( "[data-toolbox-status-filter='beta']" ) . click ( ) ;
492+ await expectActiveFilters ( [ "planned" , "wireframe" , "beta" ] ) ;
493+ await expectBuildPathChannels ( [ "planned" , "wireframe" , "beta" ] , 37 ) ;
494+
495+ expect ( failedRequests ) . toEqual ( [ ] ) ;
496+ expect ( pageErrors ) . toEqual ( [ ] ) ;
497+ expect ( consoleErrors ) . toEqual ( [ ] ) ;
498+ } finally {
499+ await workspaceV2CoverageReporter . stop ( page ) ;
500+ await server . close ( ) ;
501+ }
502+ } ) ;
503+
504+ test ( "Colors Picker Preview header controls update live and reset to defaults" , async ( { page } ) => {
505+ const server = await startRepoServer ( ) ;
506+ const failedRequests = [ ] ;
507+ const pageErrors = [ ] ;
508+ const consoleErrors = [ ] ;
458509
459- await selectBuildPathFilter ( "planned" ) ;
460- await expectBuildPathChannel ( "planned" , 28 ) ;
510+ page . on ( "response" , ( response ) => {
511+ if ( response . status ( ) >= 400 ) {
512+ failedRequests . push ( `${ response . status ( ) } ${ response . url ( ) } ` ) ;
513+ }
514+ } ) ;
515+ page . on ( "requestfailed" , ( request ) => {
516+ failedRequests . push ( `FAILED ${ request . url ( ) } ` ) ;
517+ } ) ;
518+ page . on ( "pageerror" , ( error ) => {
519+ const text = error . stack || error . message ;
520+ if ( ! isBrowserExtensionNoise ( text ) ) {
521+ pageErrors . push ( error . message ) ;
522+ }
523+ } ) ;
524+ page . on ( "console" , ( message ) => {
525+ if ( message . type ( ) === "error" && ! isBrowserExtensionNoise ( message . text ( ) ) ) {
526+ consoleErrors . push ( message . text ( ) ) ;
527+ }
528+ } ) ;
529+
530+ async function setSlider ( selector , value ) {
531+ await page . locator ( selector ) . evaluate ( ( input , nextValue ) => {
532+ input . value = String ( nextValue ) ;
533+ input . dispatchEvent ( new Event ( "input" , { bubbles : true } ) ) ;
534+ } , value ) ;
535+ }
461536
462- await selectBuildPathFilter ( "wireframe" ) ;
463- await expectBuildPathChannel ( "wireframe" , 4 ) ;
537+ async function firstPreviewFill ( ) {
538+ return page . locator ( "[data-palette-generator-preview-row]" )
539+ . first ( )
540+ . locator ( "[data-palette-generator-swatch]" )
541+ . first ( )
542+ . locator ( "rect" )
543+ . getAttribute ( "fill" ) ;
544+ }
464545
465- await selectBuildPathFilter ( "beta" ) ;
466- await expectBuildPathChannel ( "beta" , 5 ) ;
546+ try {
547+ await workspaceV2CoverageReporter . start ( page ) ;
548+ await setServerSession ( server , MOCK_DB_KEYS . users . user1 ) ;
549+ await page . goto ( `${ server . baseUrl } /toolbox/colors/index.html` , { waitUntil : "networkidle" } ) ;
550+
551+ const previewSummary = page . locator ( "[data-palette-preview-accordion] summary" ) ;
552+ const summaryOrder = await previewSummary . evaluate ( ( summary ) => (
553+ Array . from ( summary . children ) . map ( ( child ) => {
554+ if ( child . hasAttribute ( "data-palette-preview-controls" ) ) return "preview-controls" ;
555+ if ( child . querySelector ( "[data-palette-generator-preview-status]" ) ) return "preview-status" ;
556+ return child . textContent . trim ( ) ;
557+ } )
558+ ) ) ;
559+ expect ( summaryOrder ) . toEqual ( [ "Picker Preview" , "preview-controls" , "preview-status" ] ) ;
560+
561+ const previewControls = page . locator ( "[data-palette-preview-controls]" ) ;
562+ await expect ( previewControls . locator ( "[data-palette-preview-hue]" ) ) . toBeVisible ( ) ;
563+ await expect ( previewControls . locator ( "[data-palette-preview-saturation]" ) ) . toBeVisible ( ) ;
564+ await expect ( previewControls . locator ( "[data-palette-preview-brightness]" ) ) . toBeVisible ( ) ;
565+ await expect ( previewControls . locator ( "[data-palette-preview-reset]" ) ) . toHaveText ( "Default" ) ;
566+ await expect ( page . locator ( "[data-palette-preview-hue-value]" ) ) . toContainText ( "0" ) ;
567+ await expect ( page . locator ( "[data-palette-preview-saturation-value]" ) ) . toHaveText ( "100%" ) ;
568+ await expect ( page . locator ( "[data-palette-preview-brightness-value]" ) ) . toHaveText ( "100%" ) ;
569+
570+ await expect ( page . locator ( "[data-palette-generator-swatch]" ) . first ( ) ) . toBeVisible ( ) ;
571+ const initialFill = await firstPreviewFill ( ) ;
572+ await setSlider ( "[data-palette-preview-hue]" , 45 ) ;
573+ await expect ( page . locator ( "[data-palette-preview-hue-value]" ) ) . toContainText ( "+45" ) ;
574+ await expect . poll ( firstPreviewFill ) . not . toBe ( initialFill ) ;
575+ const hueAdjustedFill = await firstPreviewFill ( ) ;
576+
577+ await setSlider ( "[data-palette-preview-saturation]" , 50 ) ;
578+ await expect ( page . locator ( "[data-palette-preview-saturation-value]" ) ) . toHaveText ( "50%" ) ;
579+ await expect . poll ( firstPreviewFill ) . not . toBe ( hueAdjustedFill ) ;
580+ const saturationAdjustedFill = await firstPreviewFill ( ) ;
581+
582+ await setSlider ( "[data-palette-preview-brightness]" , 75 ) ;
583+ await expect ( page . locator ( "[data-palette-preview-brightness-value]" ) ) . toHaveText ( "75%" ) ;
584+ await expect . poll ( firstPreviewFill ) . not . toBe ( saturationAdjustedFill ) ;
585+
586+ await page . locator ( "[data-palette-preview-hue]" ) . dblclick ( ) ;
587+ await expect ( page . locator ( "[data-palette-preview-hue-value]" ) ) . toContainText ( "0" ) ;
588+
589+ await setSlider ( "[data-palette-preview-hue]" , 90 ) ;
590+ await setSlider ( "[data-palette-preview-saturation]" , 25 ) ;
591+ await setSlider ( "[data-palette-preview-brightness]" , 125 ) ;
592+ await page . locator ( "[data-palette-preview-reset]" ) . click ( ) ;
593+ await expect ( page . locator ( "[data-palette-preview-hue-value]" ) ) . toContainText ( "0" ) ;
594+ await expect ( page . locator ( "[data-palette-preview-saturation-value]" ) ) . toHaveText ( "100%" ) ;
595+ await expect ( page . locator ( "[data-palette-preview-brightness-value]" ) ) . toHaveText ( "100%" ) ;
596+ await expect ( page . locator ( "[data-palette-preview-accordion]" ) ) . toHaveJSProperty ( "open" , true ) ;
467597
468598 expect ( failedRequests ) . toEqual ( [ ] ) ;
469599 expect ( pageErrors ) . toEqual ( [ ] ) ;
0 commit comments