From a411be62556db0c6b454a758600576b1028d6746 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Mon, 4 May 2026 06:11:39 -0700 Subject: [PATCH] tests: add initial client strategy tests --- test-runner/Application.cfc | 31 ++++++ {tests => test-runner}/runner.cfm | 6 +- tests/Application.cfc | 24 ----- tests/specs/IslandClientStrategySpec.cfc | 131 +++++++++++++++++++++++ 4 files changed, 165 insertions(+), 27 deletions(-) create mode 100644 test-runner/Application.cfc rename {tests => test-runner}/runner.cfm (93%) delete mode 100644 tests/Application.cfc create mode 100644 tests/specs/IslandClientStrategySpec.cfc diff --git a/test-runner/Application.cfc b/test-runner/Application.cfc new file mode 100644 index 0000000..458eadd --- /dev/null +++ b/test-runner/Application.cfc @@ -0,0 +1,31 @@ +/** + * Copyright Since 2005 Ortus Solutions, Corp + * www.ortussolutions.com + * ************************************************************************************* + */ +component { + + this.name = "A TestBox Runner Suite"; + // any other application.cfc stuff goes below: + this.sessionManagement = true; + + // /tests points at the sibling specs directory (this app lives in /test-runner). + variables.webroot = getDirectoryFromPath( getCurrentTemplatePath() ) & "../"; + this.mappings[ "/tests" ] = variables.webroot & "tests"; + // TestBox lives under /modules/testbox in this repo; the runner expects /testbox. + this.mappings[ "/testbox" ] = variables.webroot & "modules/testbox"; + // Map /coldspa to the library directory at the webroot so specs can `new coldspa.X()`. + this.mappings[ "/coldspa" ] = variables.webroot & "coldspa"; + // Custom tag path for / (live at the webroot). + this.customTagPaths = variables.webroot; + // Turn on/off remote cfc content whitespace + this.suppressRemoteComponentContent = false; + + // any orm definitions go here. + + // request start + public boolean function onRequestStart( String targetPage ){ + return true; + } + +} diff --git a/tests/runner.cfm b/test-runner/runner.cfm similarity index 93% rename from tests/runner.cfm rename to test-runner/runner.cfm index ac4235e..e63c2ea 100644 --- a/tests/runner.cfm +++ b/test-runner/runner.cfm @@ -17,10 +17,10 @@ - - + + - + diff --git a/tests/Application.cfc b/tests/Application.cfc deleted file mode 100644 index 548dca0..0000000 --- a/tests/Application.cfc +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright Since 2005 Ortus Solutions, Corp - * www.ortussolutions.com - * ************************************************************************************* - */ -component { - - this.name = "A TestBox Runner Suite"; - // any other application.cfc stuff goes below: - this.sessionManagement = true; - - // any mappings go here, we create one that points to the root called test. - this.mappings[ "/tests" ] = getDirectoryFromPath( getCurrentTemplatePath() ); - // Turn on/off remote cfc content whitespace - this.suppressRemoteComponentContent = false; - - // any orm definitions go here. - - // request start - public boolean function onRequestStart( String targetPage ){ - return true; - } - -} diff --git a/tests/specs/IslandClientStrategySpec.cfc b/tests/specs/IslandClientStrategySpec.cfc new file mode 100644 index 0000000..7cb3d58 --- /dev/null +++ b/tests/specs/IslandClientStrategySpec.cfc @@ -0,0 +1,131 @@ +/** + * Spec: with strategy="client" + * + * The "client" strategy is the no-SSR contract: Island.cfm must NOT call the + * framework's ssrRender(), must NOT emit SSR HTML / CSS / link tags, and must + * still emit a boot script that the client uses to mount the component. + * + * We use a mock framework struct so the spec is hermetic — no Vite, no SSR + * sidecar, no network. The shape (name, clientEntry, render, ssrRender) is + * the same one Vue.cfm / React.cfm produce. + */ +component extends="testbox.system.BaseSpec" { + + function beforeAll(){ + // Pretend we're in dev so resolveAsset() doesn't try to read + // /dist/.vite/manifest.json — it'll hand back a vite URL instead. + application.coldspaConfig = { + "isDev": true, + "debug": false, + "vitePort": "5173", + "viteUrl": "http://localhost:5173", + "ssrUrl": "http://127.0.0.1:5174" + }; + } + + function run(){ + + describe( "cf_Island strategy=client", function(){ + + beforeEach( function( currentSpec ){ + // Tracks whether the framework's ssrRender() was invoked. + // strategy=client must skip it entirely. + variables.ssrCallCount = 0; + + variables.mockVue = { + "name": "Vue", + "clientEntry": "./coldspa/vite/clients/vue-client.js", + "ssrRender": function( componentGlobKey, props, slotHtml, namedSlots ){ + variables.ssrCallCount++; + return { "html": "

SHOULD NOT APPEAR

", "css": "", "error": "" }; + }, + "render": function( mountId, componentGlobKey, propsJson, resolvedClientEntry, optionsJson ){ + return { + "imports": "import { mount } from '" & arguments.resolvedClientEntry & "';", + "body": "mount('" & arguments.componentGlobKey & "', document.getElementById('" & arguments.mountId & "'), " & arguments.propsJson & ", " & arguments.optionsJson & ");" + }; + } + }; + } ); + + it( "skips SSR entirely (does not call framework.ssrRender)", function(){ + var html = renderIsland( framework = mockVue, path = "./Hello.vue", props = { "msg": "hi" }, strategy = "client" ); + expect( ssrCallCount ).toBe( 0, "ssrRender must not run when strategy=client" ); + expect( html ).notToInclude( "SHOULD NOT APPEAR" ); + } ); + + it( "emits an empty mount div (no SSR HTML inside)", function(){ + var html = renderIsland( framework = mockVue, path = "./Hello.vue", props = {}, strategy = "client" ); + // Match the empty mount div:
+ expect( html ).toMatch( '
' ); + } ); + + it( "does not emit any SSR