Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions models/Component.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ component output="true" accessors="true" {
variables._redirect = "";
variables._redirectUsingNavigate = false;
variables._isolate = false;
variables._path = "";
variables._renderedContent = "";
variables._scripts = [:];
variables._assets = [:];
Expand Down Expand Up @@ -229,7 +230,7 @@ component output="true" accessors="true" {
*/
function template( viewPath, params = {} ) {
// Normalize the view path
local.normalizedPath = variables._renderService.normalizeViewPath( arguments.viewPath );
local.normalizedPath = variables._renderService.normalizeViewPath( arguments.viewPath, variables._path );
// Render the view content and trim the result
Comment on lines 231 to 234
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

template() now always passes variables._path into normalizeViewPath(), but _path is not initialized in onDIComplete() and can be null unless _withPath() has been called. If a component instance calls template() before _withPath(), normalizeViewPath() will receive null and can error when calling find(). Consider defaulting to an empty string when _path is null, or making the new componentPath argument optional/nullable-safe in normalizeViewPath().

Copilot uses AI. Check for mistakes.
return variables._renderService.renderViewContent( this, local.normalizedPath, arguments.params );
}
Expand Down Expand Up @@ -360,7 +361,7 @@ component output="true" accessors="true" {
// Determine if component should be lazy loaded
// If lazy parameter is explicitly provided, use that value
// Otherwise, use the component's lazy preference
local.shouldLazyLoad = isNull( arguments.lazy ) ?
local.shouldLazyLoad = isNull( arguments.lazy ) ?
local.instance._getLazyLoad() : // Use component's preference if no explicit parameter
arguments.lazy; // Use explicit parameter value

Expand Down
9 changes: 5 additions & 4 deletions models/services/RenderService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ component accessors="true" singleton {

wire.set_renderedContent( local.viewContent );
return local.viewContent;
}
}

return wire.get_renderedContent();
}
Expand Down Expand Up @@ -164,14 +164,15 @@ component accessors="true" singleton {
}

/**
* Normalizes the view path for rendering. This means it will convert the dot notation path
* Normalizes the view path for rendering. This means it will convert the dot notation path
* to a slash notation path, check for the existence of .bxm or .cfm files, and ensure the path is correctly formatted.
*
* @viewPath string | The dot notation path to the view template to be rendered, without the .cfm extension.
* @componentPath string | The component path, used to determine if the normalized path should be prefixed with "wires/".
*
* @return string
*/
function normalizeViewPath( required viewPath ) {
function normalizeViewPath( required viewPath, required componentPath ) {
var paths = buildViewPaths( arguments.viewPath );

if ( paths.normalizedPath contains "cbwire/models/tmp/" ) {
Expand All @@ -190,7 +191,7 @@ component accessors="true" singleton {
throw( type="CBWIREException", message="A .bxm or .cfm template could not be found for '#arguments.viewPath#'." );
}

if ( left( paths.normalizedPath, 6 ) != "wires/" ) {
if ( !isNull( arguments.componentPath ) && !find( "@", arguments.componentPath ) && left( paths.normalizedPath, 6 ) != "wires/" ) {
paths.normalizedPath = "wires/" & paths.normalizedPath;
}
if ( left( paths.normalizedPath, 1 ) != "/" ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
component extends="cbwire.models.Component" {


data = {
"myDataPropKey" : "My Data Prop Value"
};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<cfoutput>
<div>
<cfif datePart( "h", now() ) lt 12 >
<p>Good Morning and hello CBWire Developer from a two file wire in a module!</p>
<cfelse>
<p>Good Afternoon and hello CBWire Developer from a two file wire in a module!</p>
</cfif>
</div>
</cfoutput>
17 changes: 11 additions & 6 deletions test-harness/tests/specs/CBWIRESpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,7 @@ component extends="coldbox.system.testing.BaseTestCase" {
var settings = getInstance( "coldbox:modulesettings:cbwire" );
var originalSetting = settings.csrfEnabled;
settings.csrfEnabled = false;

var payload = incomingRequest(
memo = {
"name": "TestComponent",
Expand All @@ -747,12 +747,12 @@ component extends="coldbox.system.testing.BaseTestCase" {
updates = {},
csrfToken = "badToken"
);

// Should not throw an error even with bad token when CSRF is disabled
var response = cbwireController.handleRequest( payload, event );
expect( isStruct( response ) ).toBeTrue();
expect( response.components[1].effects.html ).toInclude( "CBWIRE Slays!" );

// Restore original setting
settings.csrfEnabled = originalSetting;
} );
Expand All @@ -761,10 +761,10 @@ component extends="coldbox.system.testing.BaseTestCase" {
var settings = getInstance( "coldbox:modulesettings:cbwire" );
var originalSetting = settings.csrfEnabled;
settings.csrfEnabled = false;

var token = cbwireController.generateCSRFToken();
expect( token ).toBe( "" );

// Restore original setting
settings.csrfEnabled = originalSetting;
} );
Expand Down Expand Up @@ -1819,11 +1819,16 @@ component extends="coldbox.system.testing.BaseTestCase" {
} ).toThrow( type="ModuleNotFound" );
} );

it( "should render component from module using default wires location", function() {
it( "should render single file component from module using default wires location", function() {
var result = cbwireController.wire( "NestedModuleDefaultComponent@testingmodule" );
expect( result ).toContain( "Nested module component using default wires location" );
} );

it( "should render a two file component from module using default wires location", function() {
var result = cbwireController.wire( "twoFileModuleComponent@testingmodule" );
expect( result ).toContain( "hello CBWire Developer from a two file wire in a module" );
} );

it( "should render component from module using nested folder", function() {
var result = cbwireController.wire( "wires.nestedComponent.NestedFolderComponent@testingmodule" );
expect( result ).toContain( "Nested folder component" );
Expand Down
42 changes: 35 additions & 7 deletions test-harness/tests/specs/unit/services/RenderServiceSpec.cfc
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
component extends="coldbox.system.testing.BaseTestCase" {


function beforeAll() {
variables.mockController = createStub();
variables.mockUtility = createStub();
variables.mockChecksumService = createStub();
variables.mockValidationService = createStub();
variables.mockRequestService = createStub();

variables.renderService = prepareMock( new cbwire.models.services.RenderService() );
renderService.setCBWIREController( mockController );
renderService.setUtilityService( mockUtility );
Expand Down Expand Up @@ -263,32 +263,55 @@ component extends="coldbox.system.testing.BaseTestCase" {

it( "should return .bxm path when .bxm file exists", function() {
var input = "my.view.template";
// simulate the components variables._path which is what is passed to the wire() method
var componentPath = "my.view.template";
var bxmAbsolutePath = expandPath( "/my/view/template.bxm" );
var cfmAbsolutePath = expandPath( "/my/view/template.cfm" );
var expectedPath = "/wires/my/view/template.bxm";

mockUtility.$( "fileExists" ).$args( bxmAbsolutePath ).$results( true );
mockUtility.$( "fileExists" ).$args( cfmAbsolutePath ).$results( false );

var result = renderService.normalizeViewPath( input );
var result = renderService.normalizeViewPath( input, componentPath );
expect( result ).toBe( expectedPath );
});

it( "should return .cfm path when only .cfm file exists", function() {
var input = "my.view.template";
// simulate the components variables._path which is what is passed to the wire() method
var component_path = "my.view.template";
var bxmAbsolutePath = expandPath( "/my/view/template.bxm" );
var cfmAbsolutePath = expandPath( "/my/view/template.cfm" );
var expectedPath = "/wires/my/view/template.cfm";

mockUtility.$( "fileExists" ).$args( bxmAbsolutePath ).$results( false );
mockUtility.$( "fileExists" ).$args( cfmAbsolutePath ).$results( true );

var result = renderService.normalizeViewPath( input );
var result = renderService.normalizeViewPath( input, component_path );
expect( result ).toBe( expectedPath );
});

it( "should return .cfm module path when wire is located in module wires directory", function() {
var input = "modules_app.testingmodule.wires.twoFileModuleComponent";
// simulate the components variables._path which is what is passed to the wire() method
var component_path = "twoFileModuleComponent@testingmodule";
// use full path to module wire
var bxmAbsolutePath = expandPath( "../modules_app/testingmodule/wires/twoFileModuleComponent.bxm" );
var cfmAbsolutePath = expandPath( "../modules_app/testingmodule/wires/twoFileModuleComponent.cfm" );

var expectedPath = "/modules_app/testingmodule/wires/twoFileModuleComponent.cfm";
// mock the file exists calls for both .bxm and .cfm paths
mockUtility.$( "fileExists" ).$args( bxmAbsolutePath ).$results( false );
mockUtility.$( "fileExists" ).$args( cfmAbsolutePath ).$results( true );

var result = renderService.normalizeViewPath( input, component_path );
expect( result ).toBe( expectedPath );
});

it( "should throw when neither .bxm nor .cfm exists", function() {
var input = "my.view.template";
// simulate the components variables._path which is what is passed to the wire() method
var component_path = "my.view.template";
var bxmAbsolutePath = expandPath( "/nonexistent/view.bxm" );
var cfmAbsolutePath = expandPath( "/nonexistent/view.cfm" );
var expectedPath = "/wires/my/view/template.cfm";
Expand All @@ -297,29 +320,34 @@ component extends="coldbox.system.testing.BaseTestCase" {
mockUtility.$( "fileExists" ).$args( cfmAbsolutePath ).$results( false );

expect( function() {
renderService.normalizeViewPath( "nonexistent.view" );
renderService.normalizeViewPath( "nonexistent.view", component_path );
}).toThrow( "CBWIREException" );
});

it( "should return path with .bxm for tmp path when .bxm exists", function() {
var input = "cbwire.models.tmp.componentName";
// simulate the components variables._path which is what is passed to the wire() method
var component_path = "cbwire.models.tmp.componentName";

mockUtility.$( "fileExists" ).$args( expandPath( "/cbwire/models/tmp/componentName.bxm" ) ).$results( true );
mockUtility.$( "fileExists" ).$args( expandPath( "/cbwire/models/tmp/componentName.cfm" ) ).$results( false );

var result = renderService.normalizeViewPath( input );
var result = renderService.normalizeViewPath( input, component_path );
expect( result ).toBe( "/cbwire/models/tmp/componentName.bxm" );
});

it( "should return path with .cfm for tmp path when .cfm exists", function() {
var input = "cbwire.models.tmp.componentName";
// simulate the components variables._path which is what is passed to the wire() method
var component_path = "cbwire.models.tmp.componentName";

mockUtility.$( "fileExists" ).$args( expandPath( "/cbwire/models/tmp/componentName.bxm" ) ).$results( false );
mockUtility.$( "fileExists" ).$args( expandPath( "/cbwire/models/tmp/componentName.cfm" ) ).$results( true );

var result = renderService.normalizeViewPath( input );
var result = renderService.normalizeViewPath( input, component_path );
expect( result ).toBe( "/cbwire/models/tmp/componentName.cfm" );
});

});

describe( "getTemplatePath()", function() {
Expand Down
Loading