diff --git a/.gitignore b/.gitignore index 9638caf..9cbee4a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ crossdomain.xml node_modules/ dist/ dist-ssr/ -coldspa.config.json \ No newline at end of file +coldspa.config.json +modules/ \ No newline at end of file diff --git a/box.json b/box.json index 62fbac3..7778ca1 100644 --- a/box.json +++ b/box.json @@ -36,8 +36,12 @@ } ], "dependencies":{}, - "devDependencies":{}, - "installPaths":{}, + "devDependencies":{ + "testbox":"^7.0.0+19" + }, + "installPaths":{ + "testbox":"modules/testbox/" + }, "ignore":[ "**/.*", "/test/", @@ -46,6 +50,7 @@ "/dist/", "/dist-ssr/", "/node_modules/", + "/modules/", "/src/", "/WEB-INF/", "package.json", diff --git a/tests/Application.cfc b/tests/Application.cfc new file mode 100644 index 0000000..548dca0 --- /dev/null +++ b/tests/Application.cfc @@ -0,0 +1,24 @@ +/** + * 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/runner.cfm b/tests/runner.cfm new file mode 100644 index 0000000..ac4235e --- /dev/null +++ b/tests/runner.cfm @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/specs/BDDTest.cfc b/tests/specs/BDDTest.cfc new file mode 100644 index 0000000..313eb65 --- /dev/null +++ b/tests/specs/BDDTest.cfc @@ -0,0 +1,125 @@ +/** + * My first spec file + */ +component extends="testbox.system.BaseSpec" { + + /** + * You can prepare variables here that will be available to all specs in this spec file + */ + property testbox; + property foo; + property salvador; + + /*********************************** LIFE CYCLE Methods ***********************************/ + + /** + * Executes BEFORE all suites in this spec file + */ + function beforeAll(){ + variables.salvador = 1; + } + + /** + * Executes AFTER all suites in this spec file + */ + function afterAll(){ + // do cleanup here + } + + /*********************************** BDD SUITES ***********************************/ + + function run(){ + /** + * describe() starts a suite group of spec tests. It is the main BDD construct. + * You can also use the aliases: story(), feature(), scenario(), given(), when() + * to create fluent chains of human-readable expressions. + * + * Arguments: + * + * @title Required: The title of the suite, Usually how you want to name the desired behavior + * @body Required: A closure that will resemble the tests to execute. + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag that tells TestBox to skip this suite group from testing if true + * @focused A flag that tells TestBox to only run this suite and no other + */ + describe( "A spec", () => { + /** + * -------------------------------------------------------------------------- + * Runs before each spec in THIS suite group or nested groups + * -------------------------------------------------------------------------- + */ + beforeEach( () => { + testbox = 0; + testbox++; + } ); + + /** + * -------------------------------------------------------------------------- + * Runs after each spec in THIS suite group or nested groups + * -------------------------------------------------------------------------- + */ + afterEach( () => { + foo = 0; + } ); + + /** + * it() describes a spec to test. Usually the title is prefixed with the suite name to create an expression. + * You can also use the aliases: then(), test() to create fluent chains of human-readable expressions. + * + * Arguments: + * + * @title The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + */ + it( "can test for equality", () => { + expect( testbox ).toBe( 1 ); + } ); + + it( "can have more than one expectation to test", () => { + testbox = testbox * 8; + // type checks + expect( testbox ).toBeTypeOf( "numeric" ); + // dynamic type methods + expect( testbox ).toBeNumeric(); + // delta ranges + expect( testbox ).toBeCloseTo( expected = 10, delta = 2 ); + } ); + + it( "can have negative expectations", () => { + testbox = testbox * 8; + // type checks + expect( testbox ).notToBeTypeOf( "usdate" ); + // dynamic type methods + expect( testbox ).notToBeArray(); + // delta ranges + expect( testbox ).notToBeCloseTo( expected = 10, delta = 2 ); + } ); + + xit( "can have tests that can be skipped easily like this one by prefixing it with x", () => { + fail( "xit() this should skip" ); + } ); + + it( + title = "can have tests that execute if the right environment exists (Windows Only)", + body = () => { + expect( server.os.name ).toInclude( "Windows" ); + }, + skip = ( !isWindows() ) + ); + + it( + title = "can have tests that execute if the right environment exists (Mac Only)", + body = () => { + expect( server.os.name ).toInclude( "Mac" ); + }, + skip = ( !isMac() ) + ); + } ); + } + +} diff --git a/tests/specs/MyFirstSpec.cfc b/tests/specs/MyFirstSpec.cfc new file mode 100644 index 0000000..2cc96ca --- /dev/null +++ b/tests/specs/MyFirstSpec.cfc @@ -0,0 +1,15 @@ +component extends="testbox.system.BaseSpec"{ + + function run(){ + describe( "My First Test", ()=>{ + test( "it can add", ()=>{ + expect( sum( 1, 2 ) ).toBe( 3 ) + } ) + } ) + } + + private function sum( a, b ){ + return a + b + } + +} diff --git a/tests/test.xml b/tests/test.xml new file mode 100644 index 0000000..b17e926 --- /dev/null +++ b/tests/test.xml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Tests ran at ${start.TODAY} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.51.2 From a411be62556db0c6b454a758600576b1028d6746 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Mon, 4 May 2026 06:11:39 -0700 Subject: [PATCH 02/10] 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 ' ); + } ); + + it( "emits an immediate boot script (no idle / observer wrappers)", function(){ + var html = renderIsland( framework = mockVue, path = "./Hello.vue", props = {} ); + expect( html ).toInclude( '