fix readme repo diagram, add agents.md
Some checks failed
CI - Multi-Platform Native / Build iOS (RSSuper) (push) Has been cancelled
CI - Multi-Platform Native / Build macOS (push) Has been cancelled
CI - Multi-Platform Native / Build Android (push) Has been cancelled
CI - Multi-Platform Native / Build Linux (push) Has been cancelled
CI - Multi-Platform Native / Integration Tests (push) Has been cancelled
CI - Multi-Platform Native / Build Summary (push) Has been cancelled
Some checks failed
CI - Multi-Platform Native / Build iOS (RSSuper) (push) Has been cancelled
CI - Multi-Platform Native / Build macOS (push) Has been cancelled
CI - Multi-Platform Native / Build Android (push) Has been cancelled
CI - Multi-Platform Native / Build Linux (push) Has been cancelled
CI - Multi-Platform Native / Integration Tests (push) Has been cancelled
CI - Multi-Platform Native / Build Summary (push) Has been cancelled
This commit is contained in:
175
AGENTS.md
Normal file
175
AGENTS.md
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# AGENTS.md - RSSuper Development Guide
|
||||||
|
|
||||||
|
This file provides guidelines for AI agents working on the RSSuper codebase.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
RSSuper is a native multi-platform RSS reader:
|
||||||
|
- **iOS/macOS**: Swift + SwiftUI + Xcode
|
||||||
|
- **Android**: Kotlin + Jetpack Compose + Gradle
|
||||||
|
- **Linux**: Vala + GTK4 + Libadwaita + Meson
|
||||||
|
- **Windows**: Planned (C# + WinUI3)
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
### Main Build Script (all platforms)
|
||||||
|
```bash
|
||||||
|
./scripts/build.sh # Build all platforms (debug)
|
||||||
|
./scripts/build.sh -p ios,android # Build specific platforms
|
||||||
|
./scripts/build.sh -t release # Release build
|
||||||
|
./scripts/build.sh --test # Build and test
|
||||||
|
./scripts/build.sh -a clean # Clean all
|
||||||
|
```
|
||||||
|
|
||||||
|
### iOS/macOS
|
||||||
|
```bash
|
||||||
|
./scripts/build-ios.sh # Debug build
|
||||||
|
./scripts/build-ios.sh Release iOS build # Release build
|
||||||
|
./scripts/build-ios.sh Debug iOS test # Run tests
|
||||||
|
./scripts/build-ios.sh Debug iOS clean # Clean
|
||||||
|
|
||||||
|
# Single test file (in Xcode)
|
||||||
|
xcodebuild test -scheme RSSuper -destination 'platform=iOS Simulator,name=iPhone 15' \
|
||||||
|
-only-testing:RSSuperTests/SearchQueryTests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Android
|
||||||
|
```bash
|
||||||
|
cd android && ./gradlew assembleDebug # Debug APK
|
||||||
|
cd android && ./gradlew assembleRelease # Release APK
|
||||||
|
cd android && ./gradlew test # Run all unit tests
|
||||||
|
cd android && ./gradlew test --tests "com.rssuper.search.SearchQueryTest" # Single test class
|
||||||
|
cd android && ./gradlew clean # Clean
|
||||||
|
|
||||||
|
# Single test via Gradle
|
||||||
|
cd android && ./gradlew test --tests "com.rssuper.search.SearchQueryTest.testParse*"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
```bash
|
||||||
|
./scripts/build-linux.sh debug build # Debug build
|
||||||
|
./scripts/build-linux.sh release test # Run tests
|
||||||
|
./scripts/build-linux.sh debug clean # Clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### General Principles
|
||||||
|
- Write clean, readable code with minimal comments (only when logic is complex)
|
||||||
|
- Follow existing patterns in each platform's codebase
|
||||||
|
- Use dependency injection for testability
|
||||||
|
- Prefer explicit over implicit
|
||||||
|
|
||||||
|
### Kotlin (Android)
|
||||||
|
- **Formatting**: 4 spaces indentation, 140 char line limit
|
||||||
|
- **Naming**: `camelCase` for functions/variables, `PascalCase` for classes
|
||||||
|
- **Imports**: Grouped: standard library → Android → third-party → project
|
||||||
|
- **Types**: Use Kotlin null safety, prefer `val` over `var`
|
||||||
|
- **Error Handling**: Use `Result<T>` or sealed classes for errors (see `SearchQuery.kt`)
|
||||||
|
- **Coroutines**: Use `viewModelScope` in ViewModels, structured concurrency
|
||||||
|
- **Tests**: JUnit 4 with Mockito, Robolectric for Android-specific tests
|
||||||
|
|
||||||
|
### Swift (iOS/macOS)
|
||||||
|
- **Formatting**: 4 spaces, 120 char line limit
|
||||||
|
- **Naming**: `camelCase`, `PascalCase` for types/protocols
|
||||||
|
- **Imports**: Foundation → SwiftUI/AppKit → third-party → project
|
||||||
|
- **Error Handling**: Use `Result<T, Error>` and `async/await`
|
||||||
|
- **Protocols**: Use protocols for dependency injection (see `FeedServiceProtocol`)
|
||||||
|
- **Tests**: XCTest, arrange/act/assert pattern
|
||||||
|
|
||||||
|
### Vala (Linux)
|
||||||
|
- **Formatting**: 4 spaces indentation
|
||||||
|
- **Naming**: `snake_case` for methods/variables, `PascalCase` for classes
|
||||||
|
- **Memory**: Use GLib's reference counting (`ref`/`unref`)
|
||||||
|
- **Error Handling**: Use `GLib.Error` and `try/catch`
|
||||||
|
|
||||||
|
### Android-specific
|
||||||
|
- **ViewModels**: Use `androidx.lifecycle.ViewModel` with `StateFlow`
|
||||||
|
- **Database**: Room with Kotlin coroutines
|
||||||
|
- **Background Work**: WorkManager for sync tasks
|
||||||
|
|
||||||
|
### iOS-specific
|
||||||
|
- **State Management**: `@Observable` macro (iOS 17+) or `ObservableObject`
|
||||||
|
- **Persistence**: SQLite via SQLite.swift
|
||||||
|
- **Background**: BGTaskScheduler for background refresh
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### Clean Architecture Layers
|
||||||
|
1. **UI Layer**: SwiftUI Views / Jetpack Compose
|
||||||
|
2. **ViewModel/ViewModel**: Business logic, state management
|
||||||
|
3. **Service Layer**: `FeedService`, `SearchService`, `BookmarkStore`
|
||||||
|
4. **Data Layer**: Database managers, network clients
|
||||||
|
|
||||||
|
### Dependency Injection
|
||||||
|
- Android: Manual DI in constructors (no framework)
|
||||||
|
- iOS: Protocol-based DI with default implementations
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
### Unit Tests Location
|
||||||
|
- **Android**: `android/src/test/java/com/rssuper/`
|
||||||
|
- **iOS**: `iOS/RSSuperTests/`
|
||||||
|
- **Linux**: Test files alongside source in `linux/src/`
|
||||||
|
|
||||||
|
### Test Naming Convention
|
||||||
|
- `MethodName_State_ExpectedResult` (e.g., `SearchQueryTest.parseEmptyQueryReturnsNull`)
|
||||||
|
|
||||||
|
### Test Data
|
||||||
|
- Fixtures in `tests/fixtures/` (sample RSS/Atom feeds)
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
```
|
||||||
|
RSSuper/
|
||||||
|
├── android/ # Kotlin Android library
|
||||||
|
│ ├── src/main/java/com/rssuper/
|
||||||
|
│ │ ├── model/ # Data models
|
||||||
|
│ │ ├── parsing/ # Feed parsers
|
||||||
|
│ │ ├── state/ # State management
|
||||||
|
│ │ ├── viewmodel/ # ViewModels
|
||||||
|
│ │ ├── search/ # Search service
|
||||||
|
│ │ └── database/ # Room database
|
||||||
|
│ └── src/test/ # Unit tests
|
||||||
|
├── iOS/RSSuper/ # Swift iOS app
|
||||||
|
│ ├── Models/ # Data models
|
||||||
|
│ ├── Parsing/ # Feed parsers
|
||||||
|
│ ├── Services/ # Business logic
|
||||||
|
│ ├── ViewModels/ # ViewModels
|
||||||
|
│ ├── Networking/ # HTTP client
|
||||||
|
│ ├── Settings/ # User preferences
|
||||||
|
│ └── Database/ # SQLite layer
|
||||||
|
├── iOS/RSSuperTests/ # Unit tests
|
||||||
|
├── linux/ # Vala GTK app
|
||||||
|
│ └── src/ # Source + tests
|
||||||
|
├── scripts/ # Build scripts
|
||||||
|
└── tests/fixtures/ # Test data
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Tasks
|
||||||
|
|
||||||
|
### Running a Single Android Test
|
||||||
|
```bash
|
||||||
|
cd android
|
||||||
|
./gradlew test --tests "com.rssuper.search.SearchQueryTest"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running a Single iOS Test
|
||||||
|
```bash
|
||||||
|
./scripts/build-ios.sh Debug iOS test
|
||||||
|
# Or in Xcode: Product > Run Tests (select specific test)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding a New Platform Module
|
||||||
|
1. Create directory under `native-route/`
|
||||||
|
2. Add build command to `./scripts/build.sh`
|
||||||
|
3. Add test configuration to CI workflow
|
||||||
|
4. Update this AGENTS.md
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- The Android project uses a library module structure (not application)
|
||||||
|
- iOS minimum deployment: iOS 16.0+
|
||||||
|
- Android minimum SDK: 24 (Android 7.0)
|
||||||
|
- Linux requires GTK4, Libadwaita, SQLite3 development headers
|
||||||
|
- All platform modules share similar business logic patterns
|
||||||
@@ -39,11 +39,10 @@ RSSuper uses a native-first approach, building truly native applications for eac
|
|||||||
|
|
||||||
```
|
```
|
||||||
RSSuper/
|
RSSuper/
|
||||||
├── native-route/ # Native platform projects
|
ios/ # iOS/macOS Xcode project
|
||||||
│ ├── ios/ # iOS/macOS Xcode project
|
android/ # Android Gradle project
|
||||||
│ ├── android/ # Android Gradle project
|
linux/ # Linux Meson project
|
||||||
│ ├── linux/ # Linux Meson project
|
windows/ # Windows project (planned)
|
||||||
│ └── windows/ # Windows project (planned)
|
|
||||||
├── scripts/ # Build scripts
|
├── scripts/ # Build scripts
|
||||||
│ ├── build.sh # Main build orchestrator
|
│ ├── build.sh # Main build orchestrator
|
||||||
│ ├── build-ios.sh # iOS/macOS builder
|
│ ├── build-ios.sh # iOS/macOS builder
|
||||||
|
|||||||
@@ -328,10 +328,12 @@ class FeedIntegrationTest {
|
|||||||
|
|
||||||
val fetchResult = feedFetcher.fetch(feedUrl)
|
val fetchResult = feedFetcher.fetch(feedUrl)
|
||||||
assertTrue("Fetch should succeed", fetchResult.isSuccess())
|
assertTrue("Fetch should succeed", fetchResult.isSuccess())
|
||||||
|
|
||||||
assertThrows<Exception> {
|
try {
|
||||||
feedParser.parse(fetchResult.getOrNull()!!.feedXml, feedUrl)
|
feedParser.parse(fetchResult.getOrNull()!!.feedXml, feedUrl)
|
||||||
}
|
fail("Parsing invalid XML should throw exception")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
21
linux/gsettings/org.rssuper.app.settings.gschema.xml
Normal file
21
linux/gsettings/org.rssuper.app.settings.gschema.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<schemalist>
|
||||||
|
<schema id="org.rssuper.app.settings" path="/org/rssuper/app/settings/">
|
||||||
|
<key type="s" name="reading-prefs-file">
|
||||||
|
<default>'reading_preferences.json'</default>
|
||||||
|
<description>Reading preferences file name</description>
|
||||||
|
</key>
|
||||||
|
<key type="s" name="sync-prefs-file">
|
||||||
|
<default>'sync_preferences.json'</default>
|
||||||
|
<description>Sync preferences file name</description>
|
||||||
|
</key>
|
||||||
|
<key type="b" name="background-sync-enabled">
|
||||||
|
<default>false</default>
|
||||||
|
<description>Enable background sync</description>
|
||||||
|
</key>
|
||||||
|
<key type="i" name="sync-interval-minutes">
|
||||||
|
<default>15</default>
|
||||||
|
<description>Sync interval in minutes</description>
|
||||||
|
</key>
|
||||||
|
</schema>
|
||||||
|
</schemalist>
|
||||||
@@ -20,6 +20,19 @@ xml_dep = dependency('libxml-2.0', version: '>= 2.0')
|
|||||||
soup_dep = dependency('libsoup-3.0', version: '>= 3.0')
|
soup_dep = dependency('libsoup-3.0', version: '>= 3.0')
|
||||||
gtk_dep = dependency('gtk4', version: '>= 4.0')
|
gtk_dep = dependency('gtk4', version: '>= 4.0')
|
||||||
|
|
||||||
|
# GSettings schemas
|
||||||
|
schema_files = [
|
||||||
|
'gsettings/org.rssuper.notification.preferences.gschema.xml',
|
||||||
|
'gsettings/org.rssuper.app.settings.gschema.xml',
|
||||||
|
]
|
||||||
|
|
||||||
|
compile_schemas = custom_target('Compile GSettings schemas',
|
||||||
|
output: 'schemas.glibdir',
|
||||||
|
command: glib_dep.get_variable(name: 'glib-mkenums') + ' --targetdir=build --schema-dir=gsettings schemas.glibdir',
|
||||||
|
dependencies: glib_dep,
|
||||||
|
install: false
|
||||||
|
)
|
||||||
|
|
||||||
# Source files
|
# Source files
|
||||||
models = files(
|
models = files(
|
||||||
'src/models/feed-item.vala',
|
'src/models/feed-item.vala',
|
||||||
@@ -32,6 +45,13 @@ models = files(
|
|||||||
'src/models/bookmark.vala',
|
'src/models/bookmark.vala',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Settings files
|
||||||
|
settings = files(
|
||||||
|
'src/settings-store.vala',
|
||||||
|
'src/app-settings.vala',
|
||||||
|
'src/notification-preferences-store.vala',
|
||||||
|
)
|
||||||
|
|
||||||
# Database files
|
# Database files
|
||||||
database = files(
|
database = files(
|
||||||
'src/database/db-error.vala',
|
'src/database/db-error.vala',
|
||||||
@@ -76,6 +96,13 @@ models_lib = library('rssuper-models', models,
|
|||||||
install: false
|
install: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Settings library
|
||||||
|
settings_lib = library('rssuper-settings', settings,
|
||||||
|
dependencies: [glib_dep, gio_dep, json_dep],
|
||||||
|
link_with: [models_lib],
|
||||||
|
install: false
|
||||||
|
)
|
||||||
|
|
||||||
# Database library
|
# Database library
|
||||||
database_lib = library('rssuper-database', database,
|
database_lib = library('rssuper-database', database,
|
||||||
dependencies: [glib_dep, gio_dep, json_dep, sqlite_dep, gobject_dep],
|
dependencies: [glib_dep, gio_dep, json_dep, sqlite_dep, gobject_dep],
|
||||||
@@ -153,9 +180,19 @@ notification_manager_test_exe = executable('notification-manager-tests',
|
|||||||
install: false
|
install: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Settings store test executable
|
||||||
|
settings_store_test_exe = executable('settings-store-tests',
|
||||||
|
'src/tests/settings-store-tests.vala',
|
||||||
|
dependencies: [glib_dep, gio_dep, json_dep],
|
||||||
|
link_with: [models_lib, settings_lib],
|
||||||
|
vala_args: ['--vapidir', '.', '--pkg', 'gio-2.0'],
|
||||||
|
install: false
|
||||||
|
)
|
||||||
|
|
||||||
# Test definitions
|
# Test definitions
|
||||||
test('database tests', test_exe)
|
test('database tests', test_exe)
|
||||||
test('parser tests', parser_test_exe)
|
test('parser tests', parser_test_exe)
|
||||||
test('feed fetcher tests', fetcher_test_exe)
|
test('feed fetcher tests', fetcher_test_exe)
|
||||||
test('notification service tests', notification_service_test_exe)
|
test('notification service tests', notification_service_test_exe)
|
||||||
test('notification manager tests', notification_manager_test_exe)
|
test('notification manager tests', notification_manager_test_exe)
|
||||||
|
test('settings store tests', settings_store_test_exe)
|
||||||
|
|||||||
162
linux/src/app-settings.vala
Normal file
162
linux/src/app-settings.vala
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* app-settings.vala
|
||||||
|
*
|
||||||
|
* Application settings interface and utilities.
|
||||||
|
* Provides a unified interface for accessing application settings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using GLib;
|
||||||
|
|
||||||
|
namespace RSSuper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppSettings - Application settings interface
|
||||||
|
*
|
||||||
|
* Provides access to all application-level settings including:
|
||||||
|
* - Reading preferences
|
||||||
|
* - Sync preferences
|
||||||
|
* - Theme settings
|
||||||
|
* - Language settings
|
||||||
|
*/
|
||||||
|
public class AppSettings : Object {
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
private static AppSettings? _instance;
|
||||||
|
|
||||||
|
// Settings store
|
||||||
|
private SettingsStore? _settings_store;
|
||||||
|
|
||||||
|
// Theme
|
||||||
|
private Theme _theme;
|
||||||
|
|
||||||
|
// Language
|
||||||
|
private string _language;
|
||||||
|
|
||||||
|
// GSettings schema key
|
||||||
|
private const string SCHEMA_KEY = "org.rssuper.app.settings";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get singleton instance
|
||||||
|
*/
|
||||||
|
public static AppSettings? get_instance() {
|
||||||
|
if (_instance == null) {
|
||||||
|
_instance = new AppSettings();
|
||||||
|
}
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
private AppSettings() {
|
||||||
|
_settings_store = SettingsStore.get_instance();
|
||||||
|
|
||||||
|
// Load theme
|
||||||
|
_theme = Theme.SYSTEM;
|
||||||
|
_language = "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get reading preferences
|
||||||
|
*/
|
||||||
|
public ReadingPreferences? get_reading_preferences() {
|
||||||
|
return _settings_store.get_reading_preferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set reading preferences
|
||||||
|
*/
|
||||||
|
public void set_reading_preferences(ReadingPreferences prefs) {
|
||||||
|
_settings_store.set_reading_preferences(prefs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get background sync enabled
|
||||||
|
*/
|
||||||
|
public bool get_background_sync_enabled() {
|
||||||
|
return _settings_store.get_background_sync_enabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set background sync enabled
|
||||||
|
*/
|
||||||
|
public void set_background_sync_enabled(bool enabled) {
|
||||||
|
_settings_store.set_background_sync_enabled(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sync interval in minutes
|
||||||
|
*/
|
||||||
|
public int get_sync_interval_minutes() {
|
||||||
|
return _settings_store.get_sync_interval_minutes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set sync interval in minutes
|
||||||
|
*/
|
||||||
|
public void set_sync_interval_minutes(int minutes) {
|
||||||
|
_settings_store.set_sync_interval_minutes(minutes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get theme
|
||||||
|
*/
|
||||||
|
public Theme get_theme() {
|
||||||
|
return _theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set theme
|
||||||
|
*/
|
||||||
|
public void set_theme(Theme theme) {
|
||||||
|
_theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get language
|
||||||
|
*/
|
||||||
|
public string get_language() {
|
||||||
|
return _language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set language
|
||||||
|
*/
|
||||||
|
public void set_language(string language) {
|
||||||
|
_language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all settings as dictionary
|
||||||
|
*/
|
||||||
|
public Dictionary<string, object> get_all_settings() {
|
||||||
|
return _settings_store.get_all_settings();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set all settings from dictionary
|
||||||
|
*/
|
||||||
|
public void set_all_settings(Dictionary<string, object> settings) {
|
||||||
|
_settings_store.set_all_settings(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all settings to defaults
|
||||||
|
*/
|
||||||
|
public void reset_to_defaults() {
|
||||||
|
_settings_store.reset_to_defaults();
|
||||||
|
_theme = Theme.SYSTEM;
|
||||||
|
_language = "en";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Theme - Available theme options
|
||||||
|
*/
|
||||||
|
public enum RSSuper.Theme {
|
||||||
|
SYSTEM,
|
||||||
|
LIGHT,
|
||||||
|
DARK
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
224
linux/src/tests/settings-store-tests.vala
Normal file
224
linux/src/tests/settings-store-tests.vala
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* settings-store-tests.vala
|
||||||
|
*
|
||||||
|
* Unit tests for settings store.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using GLib;
|
||||||
|
|
||||||
|
namespace RSSuper {
|
||||||
|
|
||||||
|
public void main(string[] args) {
|
||||||
|
print("Running Settings Store Tests\n");
|
||||||
|
print("============================\n\n");
|
||||||
|
|
||||||
|
// Test ReadingPreferences
|
||||||
|
test_reading_preferences();
|
||||||
|
|
||||||
|
// Test SettingsStore
|
||||||
|
test_settings_store();
|
||||||
|
|
||||||
|
// Test AppSettings
|
||||||
|
test_app_settings();
|
||||||
|
|
||||||
|
print("\nAll tests passed!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test ReadingPreferences
|
||||||
|
*/
|
||||||
|
private void test_reading_preferences() {
|
||||||
|
print("Testing ReadingPreferences...\n");
|
||||||
|
|
||||||
|
// Test default constructor
|
||||||
|
var prefs = new ReadingPreferences();
|
||||||
|
assert(prefs.font_size == FontSize.MEDIUM);
|
||||||
|
assert(prefs.line_height == LineHeight.NORMAL);
|
||||||
|
assert(prefs.show_table_of_contents == true);
|
||||||
|
assert(prefs.show_reading_time == true);
|
||||||
|
assert(prefs.show_author == true);
|
||||||
|
assert(prefs.show_date == true);
|
||||||
|
print(" ✓ Default constructor sets correct defaults\n");
|
||||||
|
|
||||||
|
// Test with_values constructor
|
||||||
|
var prefs2 = new ReadingPreferences.with_values(
|
||||||
|
FontSize.LARGE,
|
||||||
|
LineHeight.RELAXED,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
assert(prefs2.font_size == FontSize.LARGE);
|
||||||
|
assert(prefs2.line_height == LineHeight.RELAXED);
|
||||||
|
assert(prefs2.show_table_of_contents == false);
|
||||||
|
assert(prefs2.show_reading_time == false);
|
||||||
|
assert(prefs2.show_author == false);
|
||||||
|
assert(prefs2.show_date == false);
|
||||||
|
print(" ✓ with_values constructor sets correct values\n");
|
||||||
|
|
||||||
|
// Test to_json_string
|
||||||
|
var json_str = prefs.to_json_string();
|
||||||
|
assert(json_str.contains("fontSize"));
|
||||||
|
assert(json_str.contains("lineHeight"));
|
||||||
|
assert(json_str.contains("showTableOfContents"));
|
||||||
|
print(" ✓ to_json_string produces valid JSON\n");
|
||||||
|
|
||||||
|
// Test from_json_string
|
||||||
|
var prefs3 = ReadingPreferences.from_json_string(json_str);
|
||||||
|
assert(prefs3 != null);
|
||||||
|
assert(prefs3.font_size == FontSize.MEDIUM);
|
||||||
|
print(" ✓ from_json_string parses JSON correctly\n");
|
||||||
|
|
||||||
|
// Test font_size_from_string
|
||||||
|
assert(ReadingPreferences.font_size_from_string("small") == FontSize.SMALL);
|
||||||
|
assert(ReadingPreferences.font_size_from_string("medium") == FontSize.MEDIUM);
|
||||||
|
assert(ReadingPreferences.font_size_from_string("large") == FontSize.LARGE);
|
||||||
|
assert(ReadingPreferences.font_size_from_string("xlarge") == FontSize.XLARGE);
|
||||||
|
assert(ReadingPreferences.font_size_from_string("invalid") == FontSize.MEDIUM);
|
||||||
|
print(" ✓ font_size_from_string parses correctly\n");
|
||||||
|
|
||||||
|
// Test line_height_from_string
|
||||||
|
assert(ReadingPreferences.line_height_from_string("normal") == LineHeight.NORMAL);
|
||||||
|
assert(ReadingPreferences.line_height_from_string("relaxed") == LineHeight.RELAXED);
|
||||||
|
assert(ReadingPreferences.line_height_from_string("loose") == LineHeight.LOOSE);
|
||||||
|
assert(ReadingPreferences.line_height_from_string("invalid") == LineHeight.NORMAL);
|
||||||
|
print(" ✓ line_height_from_string parses correctly\n");
|
||||||
|
|
||||||
|
// Test equals
|
||||||
|
assert(prefs.equals(prefs2) == false);
|
||||||
|
var prefs4 = new ReadingPreferences();
|
||||||
|
assert(prefs.equals(prefs4) == true);
|
||||||
|
print(" ✓ equals works correctly\n");
|
||||||
|
|
||||||
|
// Test copy_from
|
||||||
|
var prefs5 = new ReadingPreferences();
|
||||||
|
prefs5.copy_from(prefs2);
|
||||||
|
assert(prefs5.font_size == FontSize.LARGE);
|
||||||
|
assert(prefs5.line_height == LineHeight.RELAXED);
|
||||||
|
print(" ✓ copy_from copies values correctly\n");
|
||||||
|
|
||||||
|
// Test reset_to_defaults
|
||||||
|
prefs.font_size = FontSize.LARGE;
|
||||||
|
prefs.reset_to_defaults();
|
||||||
|
assert(prefs.font_size == FontSize.MEDIUM);
|
||||||
|
print(" ✓ reset_to_defaults resets to defaults\n");
|
||||||
|
|
||||||
|
print("ReadingPreferences tests passed!\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test SettingsStore
|
||||||
|
*/
|
||||||
|
private void test_settings_store() {
|
||||||
|
print("Testing SettingsStore...\n");
|
||||||
|
|
||||||
|
// Test singleton pattern
|
||||||
|
var store1 = SettingsStore.get_instance();
|
||||||
|
var store2 = SettingsStore.get_instance();
|
||||||
|
assert(store1 == store2);
|
||||||
|
print(" ✓ Singleton pattern works correctly\n");
|
||||||
|
|
||||||
|
// Test reading preferences
|
||||||
|
var prefs = new ReadingPreferences.with_values(FontSize.LARGE);
|
||||||
|
store1.set_reading_preferences(prefs);
|
||||||
|
var retrieved_prefs = store1.get_reading_preferences();
|
||||||
|
assert(retrieved_prefs != null);
|
||||||
|
assert(retrieved_prefs.font_size == FontSize.LARGE);
|
||||||
|
print(" ✓ Reading preferences stored and retrieved correctly\n");
|
||||||
|
|
||||||
|
// Test background sync enabled
|
||||||
|
store1.set_background_sync_enabled(true);
|
||||||
|
assert(store1.get_background_sync_enabled() == true);
|
||||||
|
store1.set_background_sync_enabled(false);
|
||||||
|
assert(store1.get_background_sync_enabled() == false);
|
||||||
|
print(" ✓ Background sync enabled works correctly\n");
|
||||||
|
|
||||||
|
// Test sync interval
|
||||||
|
store1.set_sync_interval_minutes(30);
|
||||||
|
assert(store1.get_sync_interval_minutes() == 30);
|
||||||
|
store1.set_sync_interval_minutes(15);
|
||||||
|
assert(store1.get_sync_interval_minutes() == 15);
|
||||||
|
print(" ✓ Sync interval works correctly\n");
|
||||||
|
|
||||||
|
// Test get_all_settings
|
||||||
|
var all_settings = store1.get_all_settings();
|
||||||
|
assert(all_settings.containsKey("fontSize"));
|
||||||
|
assert(all_settings.containsKey("backgroundSyncEnabled"));
|
||||||
|
assert(all_settings.containsKey("syncIntervalMinutes"));
|
||||||
|
print(" ✓ get_all_settings returns correct keys\n");
|
||||||
|
|
||||||
|
// Test set_all_settings
|
||||||
|
var new_settings = new Dictionary<string, object>();
|
||||||
|
new_settings["fontSize"] = "large";
|
||||||
|
new_settings["backgroundSyncEnabled"] = true;
|
||||||
|
new_settings["syncIntervalMinutes"] = 60;
|
||||||
|
store1.set_all_settings(new_settings);
|
||||||
|
assert(store1.get_reading_preferences().font_size == FontSize.LARGE);
|
||||||
|
assert(store1.get_background_sync_enabled() == true);
|
||||||
|
assert(store1.get_sync_interval_minutes() == 60);
|
||||||
|
print(" ✓ set_all_settings sets all values correctly\n");
|
||||||
|
|
||||||
|
// Test reset_to_defaults
|
||||||
|
store1.reset_to_defaults();
|
||||||
|
assert(store1.get_reading_preferences().font_size == FontSize.MEDIUM);
|
||||||
|
assert(store1.get_background_sync_enabled() == false);
|
||||||
|
assert(store1.get_sync_interval_minutes() == 15);
|
||||||
|
print(" ✓ reset_to_defaults resets all values\n");
|
||||||
|
|
||||||
|
print("SettingsStore tests passed!\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test AppSettings
|
||||||
|
*/
|
||||||
|
private void test_app_settings() {
|
||||||
|
print("Testing AppSettings...\n");
|
||||||
|
|
||||||
|
// Test singleton pattern
|
||||||
|
var settings1 = AppSettings.get_instance();
|
||||||
|
var settings2 = AppSettings.get_instance();
|
||||||
|
assert(settings1 == settings2);
|
||||||
|
print(" ✓ Singleton pattern works correctly\n");
|
||||||
|
|
||||||
|
// Test theme
|
||||||
|
settings1.set_theme(Theme.DARK);
|
||||||
|
assert(settings1.get_theme() == Theme.DARK);
|
||||||
|
settings1.set_theme(Theme.LIGHT);
|
||||||
|
assert(settings1.get_theme() == Theme.LIGHT);
|
||||||
|
settings1.set_theme(Theme.SYSTEM);
|
||||||
|
assert(settings1.get_theme() == Theme.SYSTEM);
|
||||||
|
print(" ✓ Theme works correctly\n");
|
||||||
|
|
||||||
|
// Test language
|
||||||
|
settings1.set_language("en");
|
||||||
|
assert(settings1.get_language() == "en");
|
||||||
|
settings1.set_language("es");
|
||||||
|
assert(settings1.get_language() == "es");
|
||||||
|
print(" ✓ Language works correctly\n");
|
||||||
|
|
||||||
|
// Test reading preferences through AppSettings
|
||||||
|
var prefs = new ReadingPreferences.with_values(FontSize.XLARGE);
|
||||||
|
settings1.set_reading_preferences(prefs);
|
||||||
|
var retrieved_prefs = settings1.get_reading_preferences();
|
||||||
|
assert(retrieved_prefs != null);
|
||||||
|
assert(retrieved_prefs.font_size == FontSize.XLARGE);
|
||||||
|
print(" ✓ Reading preferences through AppSettings works\n");
|
||||||
|
|
||||||
|
// Test sync settings through AppSettings
|
||||||
|
settings1.set_background_sync_enabled(true);
|
||||||
|
assert(settings1.get_background_sync_enabled() == true);
|
||||||
|
settings1.set_sync_interval_minutes(45);
|
||||||
|
assert(settings1.get_sync_interval_minutes() == 45);
|
||||||
|
print(" ✓ Sync settings through AppSettings works\n");
|
||||||
|
|
||||||
|
// Test reset_to_defaults
|
||||||
|
settings1.reset_to_defaults();
|
||||||
|
assert(settings1.get_theme() == Theme.SYSTEM);
|
||||||
|
assert(settings1.get_language() == "en");
|
||||||
|
print(" ✓ reset_to_defaults works correctly\n");
|
||||||
|
|
||||||
|
print("AppSettings tests passed!\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user