diff --git a/packages/blazor-workspace/NimbleBlazor/CONTRIBUTING.md b/packages/blazor-workspace/NimbleBlazor/CONTRIBUTING.md index 8f4b6f6a7d..82e1ba11e0 100644 --- a/packages/blazor-workspace/NimbleBlazor/CONTRIBUTING.md +++ b/packages/blazor-workspace/NimbleBlazor/CONTRIBUTING.md @@ -89,7 +89,7 @@ Test Project: `NimbleBlazor.Tests.Acceptance` In order to fully test the Nimble Blazor components, consider writing new automated acceptance tests for new/modified components. Any component which requires custom JS code in `NimbleBlazor.lib.module.js` should generally have corresponding acceptance tests, because the bUnit tests in `NimbleBlazor.Tests` are unable to exercise/test that JavaScript code. -The `NimbleBlazor.Tests.Acceptance` project starts a local Blazor Web App which serves Razor pages that host the Nimble components. Then, xUnit-based acceptance tests start a Chromium instance using [Playwright](https://playwright.dev/), load those Razor pages, and interact with them. The majority of the tests use the `InteractiveServer` render mode, but the project also supports the Interactive Web Assembly render mode (and static server-side rendering mode) for tests. +The `NimbleBlazor.Tests.Acceptance` project starts a local Blazor Web App which serves Razor pages that host the Nimble components. Then, xUnit-based acceptance tests start a Chromium instance using [Playwright](https://playwright.dev/), load those Razor pages, and interact with them. The majority of the tests use the `InteractiveServer` render mode, but the project also supports the Interactive Web Assembly render mode (and static server-side rendering mode) for tests. Tests should disable prerendering (as shown in the steps below) to ensure the components are ready for interaction when the Playwright test starts. To add a new acceptance test (with the Interactive Server render mode): - Add a new Razor file that uses that component in the `Pages.InteractiveServer` subfolder, with the name `[ComponentName][FunctionalityUnderTest].razor`, e.g. `DialogOpenAndClose.razor`. @@ -111,8 +111,8 @@ Visual Studio Code commands are included to build and run the example projects. - `blazor-server-example:watch`: Run the `Demo.Server` project in watch mode (to automatically pick up code changes) - `blazor-wasm-example:build`: Build the `Demo.Client` project - `blazor-wasm-example:watch`: Run the `Demo.Client` project in watch mode (to automatically pick up code changes) -- `blazor-hybrid-example:build`: Build the `Demo.Hybrid` project -- `blazor-hybrid-example:watch`: Run the `Demo.Hybrid` project in watch mode (to automatically pick up code changes) +- `blazor-hybrid-example:build`: (Windows only) Build the `Demo.Hybrid` project +- `blazor-hybrid-example:watch`: (Windows only) Run the `Demo.Hybrid` project in watch mode (to automatically pick up code changes) Also see the [trusting the ASP.NET Core development certificate docs](https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-8.0&tabs=visual-studio%2Clinux-sles#trust-the-aspnet-core-https-development-certificate-on-windows-and-macos). diff --git a/packages/blazor-workspace/NimbleBlazor/README.md b/packages/blazor-workspace/NimbleBlazor/README.md index 0edfa236a5..e936751960 100644 --- a/packages/blazor-workspace/NimbleBlazor/README.md +++ b/packages/blazor-workspace/NimbleBlazor/README.md @@ -101,7 +101,7 @@ Nimble supports all of the [Blazor render modes](https://learn.microsoft.com/en- - Interactive WebAssembly: Client-side rendering (CSR) using Blazor WebAssembly: `RenderMode.InteractiveWebAssembly` - Interactive Auto: Interactive SSR initially, then CSR on subsequent visits after the Blazor bundle is downloaded: `RenderMode.InteractiveAuto` - Static server-side rendering (static SSR): Default, when no render mode is specified - - ⚠️Warning: This render mode is not recommended for most use cases with Nimble. As the page is just rendered once server-side and then no state is maintained, you're unable to use event handlers or call methods on components. This also means that for components like the Nimble Table / Wafer Map, setting data can't be done vi the component methods (because they'll have no effect if called). + - ⚠️Warning: This render mode is not recommended for most use cases with Nimble. As the page is just rendered once server-side and then no state is maintained, you're unable to use event handlers or call methods on components. This also means that for components like the Nimble Table / Wafer Map, setting data can't be done via the component methods (because they'll have no effect if called). #### Prerendering diff --git a/packages/blazor-workspace/NimbleBlazor/wwwroot/NimbleBlazor.lib.module.js b/packages/blazor-workspace/NimbleBlazor/wwwroot/NimbleBlazor.lib.module.js index bac482dcc0..393e8e4d6c 100644 --- a/packages/blazor-workspace/NimbleBlazor/wwwroot/NimbleBlazor.lib.module.js +++ b/packages/blazor-workspace/NimbleBlazor/wwwroot/NimbleBlazor.lib.module.js @@ -9,192 +9,186 @@ * https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-8.0 */ -const initializer = (function () { - let hasRegisteredEvents = false; +let hasRegisteredEvents = false; +let isReady = false; - function registerEvents(Blazor) { - if (hasRegisteredEvents) { - return; - } +function registerEvents(Blazor) { + if (hasRegisteredEvents) { + return; + } - if (!Blazor) { - throw new Error('Blazor not ready to initialize Nimble with!'); - } + if (!Blazor) { + throw new Error('Blazor not ready to initialize Nimble with!'); + } - hasRegisteredEvents = true; + hasRegisteredEvents = true; - // Used by NimbleCheckbox.razor, NimbleSwitch.razor, NimbleToggleButton.razor - // Necessary because the control's value property is always just the value 'on', so we need to look - // at the checked property to correctly get the value. - Blazor.registerCustomEventType('nimblecheckedchange', { - browserEventName: 'change', - createEventArgs: event => { - return { - checked: event.target.currentChecked - }; - } - }); - // Used by NimbleTabs.razor - // Necessary because the tab control uses a 'change' event but not a value/currentValue property, - // and we do want to be notified of activeid changes (via the change event) for 2-way binding support. - // 'localName' check is required to guard against children's change event trickling into the NimbleTabs. - Blazor.registerCustomEventType('nimbletabsactiveidchange', { - browserEventName: 'change', - createEventArgs: event => { - if (event.target.localName === 'nimble-tabs') { - return { - activeId: event.target.activeid - }; - } - return null; - } - }); - // Used by NimbleMenuButton.razor - Blazor.registerCustomEventType('nimblemenubuttontoggle', { - browserEventName: 'toggle', - createEventArgs: event => { - return { - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - // Used by NimbleMenuButton.razor - Blazor.registerCustomEventType('nimblemenubuttonbeforetoggle', { - browserEventName: 'beforetoggle', - createEventArgs: event => { - return { - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - // Used by NimbleBanner.razor - Blazor.registerCustomEventType('nimblebannertoggle', { - browserEventName: 'toggle', - createEventArgs: event => { - return { - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - // Used by NimbleTable.razor - Blazor.registerCustomEventType('nimbleactionmenubeforetoggle', { - browserEventName: 'action-menu-beforetoggle', - createEventArgs: event => { - return { - newState: event.detail.newState, - oldState: event.detail.oldState, - recordIds: event.detail.recordIds, - columnId: event.detail.columnId - }; - } - }); - // Used by NimbleTable.razor - Blazor.registerCustomEventType('nimbleactionmenutoggle', { - browserEventName: 'action-menu-toggle', - createEventArgs: event => { - return { - newState: event.detail.newState, - oldState: event.detail.oldState, - recordIds: event.detail.recordIds, - columnId: event.detail.columnId - }; - } - }); - // Used by NimbleTable.razor - Blazor.registerCustomEventType('nimbletablerowselectionchange', { - browserEventName: 'selection-change', - createEventArgs: event => { - return { - selectedRecordIds: event.detail.selectedRecordIds - }; - } - }); - // Used by NimbleTable.razor - Blazor.registerCustomEventType('nimbletablecolumnconfigurationchange', { - browserEventName: 'column-configuration-change', - createEventArgs: event => { - return { - columns: event.detail.columns - }; - } - }); - // Used by NimbleTable.razor - Blazor.registerCustomEventType('nimbletablerowexpandtoggle', { - browserEventName: 'row-expand-toggle', - createEventArgs: event => { - return { - recordId: event.detail.recordId, - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - // Used by NimbleTableColumnMenuButton.razor - Blazor.registerCustomEventType('nimbletablecolumnmenubuttonbeforetoggle', { - browserEventName: 'menu-button-column-beforetoggle', - createEventArgs: event => { - return { - recordId: event.detail.recordId, - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - // Used by NimbleTableColumnMenuButton.razor - Blazor.registerCustomEventType('nimbletablecolumnmenubuttontoggle', { - browserEventName: 'menu-button-column-toggle', - createEventArgs: event => { - return { - recordId: event.detail.recordId, - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - // Used by NimbleWaferMap.razor - Blazor.registerCustomEventType('nimblewafermapdiehoverchange', { - browserEventName: 'die-hover', - createEventArgs: event => { + // Used by NimbleCheckbox.razor, NimbleSwitch.razor, NimbleToggleButton.razor + // Necessary because the control's value property is always just the value 'on', so we need to look + // at the checked property to correctly get the value. + Blazor.registerCustomEventType('nimblecheckedchange', { + browserEventName: 'change', + createEventArgs: event => { + return { + checked: event.target.currentChecked + }; + } + }); + // Used by NimbleTabs.razor + // Necessary because the tab control uses a 'change' event but not a value/currentValue property, + // and we do want to be notified of activeid changes (via the change event) for 2-way binding support. + // 'localName' check is required to guard against children's change event trickling into the NimbleTabs. + Blazor.registerCustomEventType('nimbletabsactiveidchange', { + browserEventName: 'change', + createEventArgs: event => { + if (event.target.localName === 'nimble-tabs') { return { - currentDie: event.detail.currentDie + activeId: event.target.activeid }; } - }); - } - - function handleRuntimeStarted() { - window.NimbleBlazor.isInitialized = true; - } + return null; + } + }); + // Used by NimbleMenuButton.razor + Blazor.registerCustomEventType('nimblemenubuttontoggle', { + browserEventName: 'toggle', + createEventArgs: event => { + return { + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + // Used by NimbleMenuButton.razor + Blazor.registerCustomEventType('nimblemenubuttonbeforetoggle', { + browserEventName: 'beforetoggle', + createEventArgs: event => { + return { + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + // Used by NimbleBanner.razor + Blazor.registerCustomEventType('nimblebannertoggle', { + browserEventName: 'toggle', + createEventArgs: event => { + return { + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + // Used by NimbleTable.razor + Blazor.registerCustomEventType('nimbleactionmenubeforetoggle', { + browserEventName: 'action-menu-beforetoggle', + createEventArgs: event => { + return { + newState: event.detail.newState, + oldState: event.detail.oldState, + recordIds: event.detail.recordIds, + columnId: event.detail.columnId + }; + } + }); + // Used by NimbleTable.razor + Blazor.registerCustomEventType('nimbleactionmenutoggle', { + browserEventName: 'action-menu-toggle', + createEventArgs: event => { + return { + newState: event.detail.newState, + oldState: event.detail.oldState, + recordIds: event.detail.recordIds, + columnId: event.detail.columnId + }; + } + }); + // Used by NimbleTable.razor + Blazor.registerCustomEventType('nimbletablerowselectionchange', { + browserEventName: 'selection-change', + createEventArgs: event => { + return { + selectedRecordIds: event.detail.selectedRecordIds + }; + } + }); + // Used by NimbleTable.razor + Blazor.registerCustomEventType('nimbletablecolumnconfigurationchange', { + browserEventName: 'column-configuration-change', + createEventArgs: event => { + return { + columns: event.detail.columns + }; + } + }); + // Used by NimbleTable.razor + Blazor.registerCustomEventType('nimbletablerowexpandtoggle', { + browserEventName: 'row-expand-toggle', + createEventArgs: event => { + return { + recordId: event.detail.recordId, + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + // Used by NimbleTableColumnMenuButton.razor + Blazor.registerCustomEventType('nimbletablecolumnmenubuttonbeforetoggle', { + browserEventName: 'menu-button-column-beforetoggle', + createEventArgs: event => { + return { + recordId: event.detail.recordId, + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + // Used by NimbleTableColumnMenuButton.razor + Blazor.registerCustomEventType('nimbletablecolumnmenubuttontoggle', { + browserEventName: 'menu-button-column-toggle', + createEventArgs: event => { + return { + recordId: event.detail.recordId, + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + // Used by NimbleWaferMap.razor + Blazor.registerCustomEventType('nimblewafermapdiehoverchange', { + browserEventName: 'die-hover', + createEventArgs: event => { + return { + currentDie: event.detail.currentDie + }; + } + }); +} - return { - registerEvents, - handleRuntimeStarted - }; -}()); +function handleRuntimeStarted() { + isReady = true; +} // Blazor Web Apps export function afterWebStarted(Blazor) { - initializer.registerEvents(Blazor); + registerEvents(Blazor); } // Blazor Web Apps using InteractiveServer render mode export function afterServerStarted(_Blazor) { - initializer.handleRuntimeStarted(); + handleRuntimeStarted(); } // Blazor Web Apps using InteractiveWebAssembly render mode; WASM Standalone apps export function afterWebAssemblyStarted(_Blazor) { - initializer.registerEvents(Blazor); - initializer.handleRuntimeStarted(); + registerEvents(Blazor); + handleRuntimeStarted(); } // Blazor Hybrid apps export function afterStarted(Blazor) { - initializer.registerEvents(Blazor); - initializer.handleRuntimeStarted(); + registerEvents(Blazor); + handleRuntimeStarted(); } if (window.NimbleBlazor) { @@ -202,7 +196,7 @@ if (window.NimbleBlazor) { } window.NimbleBlazor = window.NimbleBlazor ?? { - isInitialized: false, + isReady: () => isReady, Dialog: { show: async function (dialogReference) { const reason = await dialogReference.show(); diff --git a/packages/blazor-workspace/SprightBlazor/wwwroot/SprightBlazor.lib.module.js b/packages/blazor-workspace/SprightBlazor/wwwroot/SprightBlazor.lib.module.js index 3c9c408cdd..e756141518 100644 --- a/packages/blazor-workspace/SprightBlazor/wwwroot/SprightBlazor.lib.module.js +++ b/packages/blazor-workspace/SprightBlazor/wwwroot/SprightBlazor.lib.module.js @@ -9,63 +9,57 @@ * https://learn.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-8.0 */ -const initializer = (function () { - let hasRegisteredEvents = false; +let hasRegisteredEvents = false; +let isReady = false; - function registerEvents(Blazor) { - if (hasRegisteredEvents) { - return; - } - - if (!Blazor) { - throw new Error('Blazor not ready to initialize Spright with!'); - } - - hasRegisteredEvents = true; - - /* Register any custom events here - Blazor.registerCustomEventType('sprighteventname', { - browserEventName: 'foo', - createEventArgs: event => { - return { - newState: event.detail.newState, - oldState: event.detail.oldState - }; - } - }); - */ +function registerEvents(Blazor) { + if (hasRegisteredEvents) { + return; } - function handleRuntimeStarted() { - window.SprightBlazor.isInitialized = true; + if (!Blazor) { + throw new Error('Blazor not ready to initialize Spright with!'); } - return { - registerEvents, - handleRuntimeStarted - }; -}()); + hasRegisteredEvents = true; + + /* Register any custom events here + Blazor.registerCustomEventType('sprighteventname', { + browserEventName: 'foo', + createEventArgs: event => { + return { + newState: event.detail.newState, + oldState: event.detail.oldState + }; + } + }); + */ +} + +function handleRuntimeStarted() { + isReady = true; +} // Blazor Web Apps export function afterWebStarted(Blazor) { - initializer.registerEvents(Blazor); + registerEvents(Blazor); } // Blazor Web Apps using InteractiveServer render mode export function afterServerStarted(_Blazor) { - initializer.handleRuntimeStarted(); + handleRuntimeStarted(); } // Blazor Web Apps using InteractiveWebAssembly render mode; WASM Standalone apps export function afterWebAssemblyStarted(_Blazor) { - initializer.registerEvents(Blazor); - initializer.handleRuntimeStarted(); + registerEvents(Blazor); + handleRuntimeStarted(); } // Blazor Hybrid apps export function afterStarted(Blazor) { - initializer.registerEvents(Blazor); - initializer.handleRuntimeStarted(); + registerEvents(Blazor); + handleRuntimeStarted(); } if (window.SprightBlazor) { @@ -73,5 +67,5 @@ if (window.SprightBlazor) { } window.SprightBlazor = window.SprightBlazor ?? { - isInitialized: false + isReady: () => isReady }; diff --git a/packages/blazor-workspace/Tests/NimbleBlazor.Tests.Acceptance/NimbleInteractiveAcceptanceTestsBase.cs b/packages/blazor-workspace/Tests/NimbleBlazor.Tests.Acceptance/NimbleInteractiveAcceptanceTestsBase.cs index c12ccddfc9..a7006f979f 100644 --- a/packages/blazor-workspace/Tests/NimbleBlazor.Tests.Acceptance/NimbleInteractiveAcceptanceTestsBase.cs +++ b/packages/blazor-workspace/Tests/NimbleBlazor.Tests.Acceptance/NimbleInteractiveAcceptanceTestsBase.cs @@ -12,5 +12,5 @@ protected NimbleInteractiveAcceptanceTestsBase( { } - protected override string ComponentLibraryInitializationTestJavaScript => "window.NimbleBlazor && window.NimbleBlazor.isInitialized === true"; + protected override string ComponentLibraryInitializationTestJavaScript => "window.NimbleBlazor && window.NimbleBlazor.isReady()"; } diff --git a/packages/blazor-workspace/Tests/SprightBlazor.Tests.Acceptance/Tests/SprightInteractiveAcceptanceTestsBase.cs b/packages/blazor-workspace/Tests/SprightBlazor.Tests.Acceptance/Tests/SprightInteractiveAcceptanceTestsBase.cs index ae92184f3d..21a383a283 100644 --- a/packages/blazor-workspace/Tests/SprightBlazor.Tests.Acceptance/Tests/SprightInteractiveAcceptanceTestsBase.cs +++ b/packages/blazor-workspace/Tests/SprightBlazor.Tests.Acceptance/Tests/SprightInteractiveAcceptanceTestsBase.cs @@ -14,5 +14,5 @@ protected SprightInteractiveAcceptanceTestsBase( } protected override Uri ServerAddress { get; } - protected override string ComponentLibraryInitializationTestJavaScript => "window.SprightBlazor && window.SprightBlazor.isInitialized === true"; + protected override string ComponentLibraryInitializationTestJavaScript => "window.SprightBlazor && window.SprightBlazor.isReady()"; }