diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..d11e15e5b 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -16,13 +16,15 @@ After processing, the content will be moved to the main changelog and this file --> ## Added - +- Added NSIS Protocol template for Windows by @Tolfx in #4510 +- Added tests for build-assets by @Tolfx in #4510 ## Changed ## Fixed - +- Fixed linux desktop.tmpl protocol range, by removing `<.Info.Protocol>` to `<.Protocol>` by @Tolfx in #4510 +- Fixed redefinition error for liquid glass demo in [#4542](https://github.com/wailsapp/wails/pull/4542) by @Etesam913 ## Deprecated diff --git a/v3/internal/commands/build-assets_test.go b/v3/internal/commands/build-assets_test.go new file mode 100644 index 000000000..2b16cafd5 --- /dev/null +++ b/v3/internal/commands/build-assets_test.go @@ -0,0 +1,267 @@ +package commands + +import ( + "os" + "path/filepath" + "testing" + + "gopkg.in/yaml.v3" +) + +func TestGenerateBuildAssets(t *testing.T) { + // Create a temporary directory for testing + tempDir, err := os.MkdirTemp("", "wails-build-assets-test-*") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) + + tests := []struct { + name string + options *BuildAssetsOptions + wantErr bool + }{ + { + name: "Basic build assets generation", + options: &BuildAssetsOptions{ + Dir: "testbuild", + Name: "TestApp", + BinaryName: "", + ProductName: "Test Application", + ProductDescription: "A test application", + ProductVersion: "1.0.0", + ProductCompany: "Test Company", + ProductCopyright: "© 2024 Test Company", + ProductComments: "Test comments", + ProductIdentifier: "", + Silent: true, + }, + wantErr: false, + }, + { + name: "Build assets with custom binary name", + options: &BuildAssetsOptions{ + Dir: "testbuild2", + Name: "Custom App", + BinaryName: "custom-binary", + ProductName: "Custom Application", + ProductDescription: "A custom application", + ProductVersion: "2.0.0", + ProductCompany: "Custom Company", + ProductIdentifier: "com.custom.app", + Silent: true, + }, + wantErr: false, + }, + { + name: "Build assets with MSIX options", + options: &BuildAssetsOptions{ + Dir: "testbuild3", + Name: "MSIX App", + ProductName: "MSIX Application", + ProductDescription: "An MSIX application", + ProductVersion: "3.0.0", + ProductCompany: "MSIX Company", + Publisher: "CN=MSIX Company", + ProcessorArchitecture: "x64", + ExecutablePath: "msix-app.exe", + ExecutableName: "msix-app.exe", + OutputPath: "msix-app.msix", + Silent: true, + }, + wantErr: false, + }, + { + name: "Build assets with TypeScript", + options: &BuildAssetsOptions{ + Dir: "testbuild4", + Name: "TypeScript App", + ProductName: "TypeScript Application", + ProductDescription: "A TypeScript application", + ProductVersion: "4.0.0", + ProductCompany: "TypeScript Company", + Typescript: true, + Silent: true, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set the directory to be under our temp directory + buildDir := filepath.Join(tempDir, tt.options.Dir) + tt.options.Dir = buildDir + + err := GenerateBuildAssets(tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("GenerateBuildAssets() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + // Verify that the build directory was created + if _, err := os.Stat(buildDir); os.IsNotExist(err) { + t.Errorf("Build directory %s was not created", buildDir) + } + + // List all files that were actually created for debugging + files, err := os.ReadDir(buildDir) + if err != nil { + t.Errorf("Failed to read build directory: %v", err) + } else { + t.Logf("Files created in %s:", buildDir) + for _, file := range files { + t.Logf(" - %s", file.Name()) + } + } + + // Verify some expected files were created - check what actually exists + expectedFiles := []string{ + "config.yml", + "appicon.png", + "Taskfile.yml", + } + + for _, file := range expectedFiles { + filePath := filepath.Join(buildDir, file) + if _, err := os.Stat(filePath); os.IsNotExist(err) { + t.Errorf("Expected file %s was not created", file) + } + } + + // Test that defaults were applied correctly + if tt.options.ProductIdentifier == "" && tt.options.Name != "" { + expectedIdentifier := "com.wails." + normaliseName(tt.options.Name) + // We can't easily check this without modifying the function to return the config + // but we know the logic is there + _ = expectedIdentifier + } + } + }) + } +} + +func TestUpdateBuildAssets(t *testing.T) { + // Create a temporary directory for testing + tempDir, err := os.MkdirTemp("", "wails-update-assets-test-*") + if err != nil { + t.Fatalf("Failed to create temp directory: %v", err) + } + defer os.RemoveAll(tempDir) + + // Create a sample wails config file + configDir := filepath.Join(tempDir, "config") + err = os.MkdirAll(configDir, 0755) + if err != nil { + t.Fatalf("Failed to create config directory: %v", err) + } + + configFile := filepath.Join(configDir, "wails.yaml") + config := WailsConfig{ + Info: struct { + CompanyName string `yaml:"companyName"` + ProductName string `yaml:"productName"` + ProductIdentifier string `yaml:"productIdentifier"` + Description string `yaml:"description"` + Copyright string `yaml:"copyright"` + Comments string `yaml:"comments"` + Version string `yaml:"version"` + }{ + CompanyName: "Config Company", + ProductName: "Config Product", + ProductIdentifier: "com.config.product", + Description: "Config Description", + Copyright: "© 2024 Config Company", + Comments: "Config Comments", + Version: "1.0.0", + }, + FileAssociations: []FileAssociation{ + { + Ext: ".test", + Name: "Test File", + Description: "Test file association", + IconName: "test-icon", + Role: "Editor", + MimeType: "application/test", + }, + }, + Protocols: []ProtocolConfig{ + { + Scheme: "testapp", + Description: "Test App Protocol", + }, + }, + } + + configBytes, err := yaml.Marshal(config) + if err != nil { + t.Fatalf("Failed to marshal config: %v", err) + } + + err = os.WriteFile(configFile, configBytes, 0644) + if err != nil { + t.Fatalf("Failed to write config file: %v", err) + } + + tests := []struct { + name string + options *UpdateBuildAssetsOptions + wantErr bool + }{ + { + name: "Update with config file", + options: &UpdateBuildAssetsOptions{ + Dir: "updatebuild1", + Name: "UpdateApp", + Config: configFile, + Silent: true, + }, + wantErr: false, + }, + { + name: "Update without config file", + options: &UpdateBuildAssetsOptions{ + Dir: "updatebuild2", + Name: "UpdateApp2", + ProductName: "Update Application 2", + ProductDescription: "An update application 2", + ProductVersion: "2.0.0", + ProductCompany: "Update Company 2", + Silent: true, + }, + wantErr: false, + }, + { + name: "Update with non-existent config file", + options: &UpdateBuildAssetsOptions{ + Dir: "updatebuild3", + Name: "UpdateApp3", + Config: "non-existent-config.yaml", + Silent: true, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set the directory to be under our temp directory + updateDir := filepath.Join(tempDir, tt.options.Dir) + tt.options.Dir = updateDir + + err := UpdateBuildAssets(tt.options) + if (err != nil) != tt.wantErr { + t.Errorf("UpdateBuildAssets() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if !tt.wantErr { + // Verify that the update directory was created + if _, err := os.Stat(updateDir); os.IsNotExist(err) { + t.Errorf("Update directory %s was not created", updateDir) + } + } + }) + } +} diff --git a/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl b/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl index 404fae750..df4bd5438 100644 --- a/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl +++ b/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl @@ -92,7 +92,8 @@ Section CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" !insertmacro wails.associateFiles - + !insertmacro wails.associateCustomProtocols + !insertmacro wails.writeUninstaller SectionEnd @@ -107,6 +108,7 @@ Section "uninstall" Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols !insertmacro wails.deleteUninstaller SectionEnd diff --git a/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl b/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl index 06f42314f..ab22e2be4 100644 --- a/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl +++ b/v3/internal/commands/updatable_build_assets/linux/desktop.tmpl @@ -11,5 +11,5 @@ Categories=Utility; StartupWMClass={{.BinaryName}} {{if .Protocols -}} -MimeType={{range $index, $protocol := .Info.Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}} +MimeType={{range $index, $protocol := .Protocols}}x-scheme-handler/{{$protocol.Scheme}};{{end}} {{- end}} diff --git a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl index aae481317..b4b0c2b4e 100644 --- a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl +++ b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl @@ -215,4 +215,32 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" Delete "$INSTDIR\{{.IconName}}.ico" {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} !macroend \ No newline at end of file