wails/v3/pkg/application/services_test.go
Lea Anthony 2604ecc0f8
test(v3): add comprehensive unit tests for pkg/application (#4827)
* test(v3): add comprehensive unit tests for pkg/application

Add 11 new test files to improve test coverage of the pkg/application
package from 13.6% to 17.7%.

New test files:
- context_test.go: Context struct operations
- services_test.go: Service management and lifecycle
- parameter_test.go: Parameter and CallError types
- dialogs_test.go: Dialog utilities and button methods
- webview_window_options_test.go: Window options and constants
- application_options_test.go: ChainMiddleware and app config
- keys_test.go: Keyboard accelerator parsing
- single_instance_test.go: Single instance management and encryption
- menuitem_internal_test.go: Menu item internal functions
- menu_internal_test.go: Menu internal functions
- screenmanager_internal_test.go: Screen geometry and transformations

The 40% target was not fully achievable because ~50% of the codebase
is platform-specific code that can only be tested on respective
platforms. Tests focus on pure Go logic, utility functions, and
data structures that can be tested cross-platform.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address CodeRabbit review comments

- dialogs_test.go: improve ID recycling test to verify either recycled
  ID (id3 == id1) or new unique ID (id3 > id2)
- keys_test.go: make accelerator String() tests platform-agnostic by
  checking suffix patterns rather than exact platform-specific strings

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: normalize temp dir path for macOS compatibility in test

os.TempDir() returns a trailing slash on macOS, causing path comparison
to fail. Use filepath.Clean() to normalize both paths.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove REVIEW.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: simplify changelog entry

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 09:53:24 +11:00

203 lines
5.2 KiB
Go

package application
import (
"context"
"testing"
)
// Test service implementations
type testService struct {
name string
}
func (s *testService) ServiceName() string {
return s.name
}
type testServiceWithStartup struct {
started bool
}
func (s *testServiceWithStartup) ServiceStartup(ctx context.Context, options ServiceOptions) error {
s.started = true
return nil
}
type testServiceWithShutdown struct {
shutdown bool
}
func (s *testServiceWithShutdown) ServiceShutdown() error {
s.shutdown = true
return nil
}
type testServiceNoInterface struct {
value int
}
func TestNewService(t *testing.T) {
svc := &testService{name: "test"}
service := NewService(svc)
if service.instance == nil {
t.Error("NewService should set instance")
}
if service.Instance() != svc {
t.Error("Instance() should return the original service")
}
}
func TestNewServiceWithOptions(t *testing.T) {
svc := &testService{name: "original"}
opts := ServiceOptions{
Name: "custom-name",
Route: "/api",
}
service := NewServiceWithOptions(svc, opts)
if service.Instance() != svc {
t.Error("Instance() should return the original service")
}
if service.options.Name != "custom-name" {
t.Errorf("options.Name = %q, want %q", service.options.Name, "custom-name")
}
if service.options.Route != "/api" {
t.Errorf("options.Route = %q, want %q", service.options.Route, "/api")
}
}
func TestGetServiceName_FromOptions(t *testing.T) {
svc := &testService{name: "service-name"}
opts := ServiceOptions{Name: "options-name"}
service := NewServiceWithOptions(svc, opts)
name := getServiceName(service)
if name != "options-name" {
t.Errorf("getServiceName() = %q, want %q (options takes precedence)", name, "options-name")
}
}
func TestGetServiceName_FromInterface(t *testing.T) {
svc := &testService{name: "interface-name"}
service := NewService(svc)
name := getServiceName(service)
if name != "interface-name" {
t.Errorf("getServiceName() = %q, want %q (from interface)", name, "interface-name")
}
}
func TestGetServiceName_FromType(t *testing.T) {
svc := &testServiceNoInterface{value: 42}
service := NewService(svc)
name := getServiceName(service)
// Should contain the type name
if name == "" {
t.Error("getServiceName() should return type name for services without ServiceName interface")
}
// The name should contain "testServiceNoInterface"
expected := "application.testServiceNoInterface"
if name != expected {
t.Errorf("getServiceName() = %q, want %q", name, expected)
}
}
func TestService_Instance(t *testing.T) {
svc := &testService{name: "test"}
service := NewService(svc)
instance := service.Instance()
if instance == nil {
t.Error("Instance() should not return nil")
}
// Type assertion to verify it's the correct type
if _, ok := instance.(*testService); !ok {
t.Error("Instance() should return the correct type")
}
}
func TestDefaultServiceOptions(t *testing.T) {
// Verify DefaultServiceOptions is zero-valued
if DefaultServiceOptions.Name != "" {
t.Errorf("DefaultServiceOptions.Name should be empty, got %q", DefaultServiceOptions.Name)
}
if DefaultServiceOptions.Route != "" {
t.Errorf("DefaultServiceOptions.Route should be empty, got %q", DefaultServiceOptions.Route)
}
if DefaultServiceOptions.MarshalError != nil {
t.Error("DefaultServiceOptions.MarshalError should be nil")
}
}
func TestNewService_UsesDefaultOptions(t *testing.T) {
svc := &testService{name: "test"}
service := NewService(svc)
// Service created with NewService should use DefaultServiceOptions
if service.options.Name != DefaultServiceOptions.Name {
t.Error("NewService should use DefaultServiceOptions")
}
}
func TestServiceOptions_WithMarshalError(t *testing.T) {
customMarshal := func(err error) []byte {
return []byte(`{"error": "custom"}`)
}
svc := &testService{name: "test"}
opts := ServiceOptions{
MarshalError: customMarshal,
}
service := NewServiceWithOptions(svc, opts)
if service.options.MarshalError == nil {
t.Error("MarshalError should be set")
}
result := service.options.MarshalError(nil)
expected := `{"error": "custom"}`
if string(result) != expected {
t.Errorf("MarshalError result = %q, want %q", string(result), expected)
}
}
func TestServiceStartupInterface(t *testing.T) {
svc := &testServiceWithStartup{}
service := NewService(svc)
// Verify the service implements ServiceStartup
instance := service.Instance()
if startup, ok := instance.(ServiceStartup); ok {
err := startup.ServiceStartup(context.Background(), ServiceOptions{})
if err != nil {
t.Errorf("ServiceStartup returned error: %v", err)
}
if !svc.started {
t.Error("ServiceStartup should have been called")
}
} else {
t.Error("testServiceWithStartup should implement ServiceStartup")
}
}
func TestServiceShutdownInterface(t *testing.T) {
svc := &testServiceWithShutdown{}
service := NewService(svc)
// Verify the service implements ServiceShutdown
instance := service.Instance()
if shutdown, ok := instance.(ServiceShutdown); ok {
err := shutdown.ServiceShutdown()
if err != nil {
t.Errorf("ServiceShutdown returned error: %v", err)
}
if !svc.shutdown {
t.Error("ServiceShutdown should have been called")
}
} else {
t.Error("testServiceWithShutdown should implement ServiceShutdown")
}
}