diff --git a/interceptors/CBWIRE.cfc b/interceptors/CBWIRE.cfc index 6463489e..eed7a648 100644 --- a/interceptors/CBWIRE.cfc +++ b/interceptors/CBWIRE.cfc @@ -140,12 +140,65 @@ component { function postLayoutRender() { if ( shouldInject( arguments.event ) && !request.keyExists( "_cbwire_injected_assets" ) ) { + // Get the layout file content to check for required tags + local.layoutContent = getLayoutContent( arguments.event ); + + // Check if the layout has required tags for asset injection + if ( !findNoCase( "", local.layoutContent ) ) { + throw( + type = "CBWIREException", + message = "Layout is missing tag required for wireStyles() injection.", + detail = "Your layout must include a tag where CBWIRE can inject CSS styles. Either add a tag to your layout or set 'autoInjectAssets' to false and manually call wireStyles() and wireScripts()." + ); + } + if ( !findNoCase( "", local.layoutContent ) ) { + throw( + type = "CBWIREException", + message = "Layout is missing tag required for wireScripts() injection.", + detail = "Your layout must include a tag where CBWIRE can inject JavaScript. Either add a tag to your layout or set 'autoInjectAssets' to false and manually call wireStyles() and wireScripts()." + ); + } + arguments.data.renderedLayout = replaceNoCase( arguments.data.renderedLayout, "", getStyles() & chr( 10 ) & "", "one" ); arguments.data.renderedLayout = replaceNoCase( arguments.data.renderedLayout, "", getScripts() & chr( 10 ) & "", "one" ); request._cbwire_injected_assets = true; } } + /** + * Gets the layout file content for validation. + * Reads the actual layout file from disk based on the current layout name. + * + * @event The request context object + * + * @return string The layout file content + */ + private function getLayoutContent( event ) { + // Get the current layout name from the private collection + local.layoutName = arguments.event.getPrivateValue( "currentLayout", "" ); + + if ( !len( local.layoutName ) ) { + return ""; + } + + // Get the layouts directory path + local.layoutsPath = expandPath( "/layouts/" ); + + // Try common file extensions + local.extensions = [ ".cfm", ".bxm" ]; + + for ( local.ext in local.extensions ) { + local.layoutFile = local.layoutsPath & local.layoutName & local.ext; + if ( fileExists( local.layoutFile ) ) { + return fileRead( local.layoutFile ); + } + } + + // If we can't find the layout file, return empty string + // This will allow the existing rendered content check to fail gracefully + return ""; + } + function preModuleLoad() eventPattern="^cbwire.*" { return true; } diff --git a/test-harness/handlers/Tests.cfc b/test-harness/handlers/Tests.cfc new file mode 100644 index 00000000..1033a0c4 --- /dev/null +++ b/test-harness/handlers/Tests.cfc @@ -0,0 +1,21 @@ +/** + * Handler for testing missing layout tags + */ +component { + + function testMissingHeadTag( event, rc, prc ) { + event.setLayout( "MissingHeadTag" ); + event.setView( "tests/missingtags" ); + } + + function testMissingBodyTag( event, rc, prc ) { + event.setLayout( "MissingBodyTag" ); + event.setView( "tests/missingtags" ); + } + + function testMissingBothTags( event, rc, prc ) { + event.setLayout( "MissingBothTags" ); + event.setView( "tests/missingtags" ); + } + +} diff --git a/test-harness/layouts/MissingBodyTag.cfm b/test-harness/layouts/MissingBodyTag.cfm new file mode 100644 index 00000000..21aef2e1 --- /dev/null +++ b/test-harness/layouts/MissingBodyTag.cfm @@ -0,0 +1,9 @@ + + + + + Missing Body Tag + + #view()# + + diff --git a/test-harness/layouts/MissingBothTags.cfm b/test-harness/layouts/MissingBothTags.cfm new file mode 100644 index 00000000..4b520785 --- /dev/null +++ b/test-harness/layouts/MissingBothTags.cfm @@ -0,0 +1,6 @@ + + + + #view()# + + diff --git a/test-harness/layouts/MissingHeadTag.cfm b/test-harness/layouts/MissingHeadTag.cfm new file mode 100644 index 00000000..39698769 --- /dev/null +++ b/test-harness/layouts/MissingHeadTag.cfm @@ -0,0 +1,8 @@ + + + + + #view()# + + + diff --git a/test-harness/tests/specs/CBWIRESpec.cfc b/test-harness/tests/specs/CBWIRESpec.cfc index 265eff9e..03d10c68 100644 --- a/test-harness/tests/specs/CBWIRESpec.cfc +++ b/test-harness/tests/specs/CBWIRESpec.cfc @@ -127,6 +127,33 @@ component extends="coldbox.system.testing.BaseTestCase" { expect( beforeHead ).toInclude( "" ); } ); + it( "should throw exception when layout is missing tag with autoInjectAssets enabled", function() { + var settings = getInstance( "coldbox:modulesettings:cbwire" ); + settings.autoInjectAssets = true; + expect( function() { + this.get( "tests.testMissingHeadTag" ); + } ).toThrow( type="CBWIREException", message="Layout is missing tag required for wireStyles() injection." ); + } ); + + it( "should throw exception when layout is missing tag with autoInjectAssets enabled", function() { + var settings = getInstance( "coldbox:modulesettings:cbwire" ); + settings.autoInjectAssets = true; + expect( function() { + this.get( "tests.testMissingBodyTag" ); + } ).toThrow( type="CBWIREException", message="Layout is missing tag required for wireScripts() injection." ); + } ); + + it( "should not throw exception when layout is missing tags with autoInjectAssets disabled", function() { + var settings = getInstance( "coldbox:modulesettings:cbwire" ); + settings.autoInjectAssets = false; + var event = this.get( "tests.testMissingBothTags" ); + var html = event.getRenderedContent(); + // Should render without error + expect( html ).toBeString(); + expect( html ).notToInclude( "" ); + expect( html ).notToInclude( "" ); + } ); + } ); describe("Component.cfc", function() { diff --git a/test-harness/tests/specs/FileUploadSpec.cfc b/test-harness/tests/specs/FileUploadSpec.cfc index 2ba399e7..20d2107c 100644 --- a/test-harness/tests/specs/FileUploadSpec.cfc +++ b/test-harness/tests/specs/FileUploadSpec.cfc @@ -139,14 +139,15 @@ component extends="coldbox.system.testing.BaseTestCase" { var result = loadMockedFileUpload( "test-store-file", "text", "plain" ); var destinationDir = getTempDirectory() & "/cbwire-test-store-file"; var destinationPath = destinationDir & "/myfile.png"; - + // Store the file with specific filename var storedPath = result.store( destinationPath ); - + // Verify the file was moved to the destination with the new name expect( fileExists( storedPath ) ).toBeTrue(); - expect( storedPath ).toBe( getCanonicalPath( destinationPath ) ); - + // Normalize both paths to handle platform differences (e.g., /var vs /private/var on macOS) + expect( getCanonicalPath( storedPath ) ).toBe( getCanonicalPath( destinationPath ) ); + // Clean up if ( directoryExists( destinationDir ) ) { directoryDelete( destinationDir, true ); diff --git a/test-harness/views/tests/missingtags.cfm b/test-harness/views/tests/missingtags.cfm new file mode 100644 index 00000000..54f6aa73 --- /dev/null +++ b/test-harness/views/tests/missingtags.cfm @@ -0,0 +1,3 @@ + + #wire( "Counter" )# +