Skip to content
Draft
Show file tree
Hide file tree
Changes from 7 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
14 changes: 13 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ cd packages/luthor_generator/example
dart run build_runner build -d
```

### Testing luthor_generator
The `luthor_generator` package has tests that use generated code from test fixtures:
```bash
cd packages/luthor_generator
# Generate code for test fixtures
dart run build_runner build --delete-conflicting-outputs
# Run tests
dart test
```

Tests are automatically run in CI which includes building the test fixtures.

### Documentation
The documentation site is located in `./docs` and is built with Astro.

Expand Down Expand Up @@ -254,7 +266,7 @@ For each annotated class, generate:
### Supporting Code Generation
1. Create annotation in `lib/src/annotations/validators/`
2. Update generator to handle the new annotation
3. Add tests for generated code
3. Add tests for generated code in `test/` using fixtures
4. Update example to demonstrate usage

## Resources
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup-luthor
Copy link
Owner

Choose a reason for hiding this comment

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

Give this a name

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added name "Build generator test fixtures" in commit 93ebb0a.

Copy link
Owner

@exaby73 exaby73 Jan 1, 2026

Choose a reason for hiding this comment

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

uses: ./.github/actions/setup-luthor

@copilot I want a name to the above like

Suggested change
- uses: ./.github/actions/setup-luthor
name: Setup project
uses: ./.github/actions/setup-luthor

Copy link
Owner

Choose a reason for hiding this comment

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

@copilot See above comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added "Setup project" name to the step in commit d984f2c.

- name: Build generator test fixtures
run: cd packages/luthor_generator && dart run build_runner build --delete-conflicting-outputs
- run: dpk run test
6 changes: 6 additions & 0 deletions dpk.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,16 @@ scripts:
runInPackages:
- packages/*

build:
command: dart run build_runner build --force-aot
runInPackages:
- packages/luthor_generator

test:
command: dart test
runInPackages:
- packages/luthor
- packages/luthor_generator

publish:luthor:
command: dart pub publish -f
Expand Down
10 changes: 9 additions & 1 deletion packages/luthor_generator/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ builders:
import: "package:luthor_generator/builder.dart"
builder_factories: ["luthorBuilder"]
build_extensions: { ".dart": [".luthor.g.part"] }
auto_apply: dependents
auto_apply: all_packages
Copy link
Owner

Choose a reason for hiding this comment

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

This change may have been unintentional

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're correct - reverted to auto_apply: dependents in commit b8e1267. The targets section ensures the builder still runs on test fixtures while keeping the proper production setting.

build_to: cache
applies_builders: ["source_gen:combining_builder"]

targets:
$default:
sources:
- lib/**
- test/**
- $package$
5 changes: 5 additions & 0 deletions packages/luthor_generator/example/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
targets:
$default:
builders:
luthor_generator:luthor:
enabled: true
6 changes: 3 additions & 3 deletions packages/luthor_generator/example/lib/sample.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import 'package:analyzer/dart/element/element2.dart';
import 'package:luthor_generator/checkers.dart';
import 'package:luthor_generator/helpers/validations/base_validations.dart';

/// Returns true if the string should be a raw string (contains backslashes)
bool _shouldUseRawString(String value) {
return value.contains(r'\');
}

/// Formats a string literal, using raw string only if needed
String _formatStringLiteral(String value) {
if (_shouldUseRawString(value)) {
return 'r"$value"';
}
return '"$value"';
}

String getStringValidations(FormalParameterElement param) {
final buffer = StringBuffer();

Expand Down Expand Up @@ -239,7 +252,7 @@ void _checkAndWriteStartsWithValidation(
.getField('messageFn')
?.toFunctionValue();

final params = <String>['r"$string"'];
final params = <String>[_formatStringLiteral(string!)];
if (message != null) params.add("message: '$message'");
if (messageFn != null) {
params.add("messageFn: ${getQualifiedFunctionName(messageFn)}");
Expand All @@ -264,7 +277,7 @@ void _checkAndWriteEndsWithValidation(
.getField('messageFn')
?.toFunctionValue();

final params = <String>['r"$string"'];
final params = <String>[_formatStringLiteral(string!)];
if (message != null) params.add("message: '$message'");
if (messageFn != null) {
params.add("messageFn: ${getQualifiedFunctionName(messageFn)}");
Expand All @@ -289,7 +302,7 @@ void _checkAndWriteContainsValidation(
.getField('messageFn')
?.toFunctionValue();

final params = <String>['r"$string"'];
final params = <String>[_formatStringLiteral(string!)];
if (message != null) params.add("message: '$message'");
if (messageFn != null) {
params.add("messageFn: ${getQualifiedFunctionName(messageFn)}");
Expand Down
3 changes: 3 additions & 0 deletions packages/luthor_generator/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@ dependencies:
source_gen: ^4.0.0 # Configured via catalog

dev_dependencies:
build_runner: ^2.4.0
freezed: ^3.0.3
json_serializable: ^6.7.0
lint: ^2.3.0
test: ^1.25.14
97 changes: 97 additions & 0 deletions packages/luthor_generator/test/custom_validator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import 'package:luthor/luthor.dart';
import 'package:test/test.dart';

import 'fixtures/custom_validator_model.dart';

void main() {
group('Custom Validator Tests', () {
test('should generate schema with custom validators', () {
expect($CustomValidatorModelSchema, isA<Validator>());
});

test('should validate @WithCustomValidator - valid case', () {
final validData = {
'customField': 'valid',
'matchField': 'test',
'confirmField': 'test',
};

final result = $CustomValidatorModelValidate(validData);

switch (result) {
case SchemaValidationSuccess(data: final model):
expect(model.customField, equals('valid'));
case SchemaValidationError(errors: final errors):
fail('Should not have validation errors: $errors');
}
});

test(
'should validate @WithCustomValidator - invalid case with custom message',
() {
final invalidData = {
'customField': 'invalid',
'matchField': 'test',
'confirmField': 'test',
};

final result = $CustomValidatorModelValidate(invalidData);

switch (result) {
case SchemaValidationSuccess(data: _):
fail('Should have validation error for custom validator');
case SchemaValidationError(errors: final errors):
expect(errors.keys, contains('customField'));
expect(errors['customField'], isNotNull);
expect(
(errors['customField']! as List).first,
equals('Value must be "valid"'),
);
}
},
);

test('should validate @WithSchemaCustomValidator - matching fields', () {
final validData = {
'customField': 'valid',
'matchField': 'password123',
'confirmField': 'password123',
};

final result = $CustomValidatorModelValidate(validData);

switch (result) {
case SchemaValidationSuccess(data: final model):
expect(model.matchField, equals('password123'));
expect(model.confirmField, equals('password123'));
case SchemaValidationError(errors: final errors):
fail('Should not have validation errors: $errors');
}
});

test(
'should validate @WithSchemaCustomValidator - non-matching fields',
() {
final invalidData = {
'customField': 'valid',
'matchField': 'password123',
'confirmField': 'different',
};

final result = $CustomValidatorModelValidate(invalidData);

switch (result) {
case SchemaValidationSuccess(data: _):
fail('Should have validation error for non-matching fields');
case SchemaValidationError(errors: final errors):
expect(errors.keys, contains('confirmField'));
expect(errors['confirmField'], isNotNull);
expect(
(errors['confirmField']! as List).first,
equals('Fields must match'),
);
}
},
);
});
}
23 changes: 23 additions & 0 deletions packages/luthor_generator/test/fixtures/annotated_model.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:luthor/luthor.dart';

part 'annotated_model.freezed.dart';
part 'annotated_model.g.dart';

@luthor
@freezed
abstract class AnnotatedModel with _$AnnotatedModel {
const factory AnnotatedModel({
@IsEmail() required String email,
@HasMin(8) @HasMax(100) required String password,
@HasMin(18) @HasMax(120) required int age,
@HasMinDouble(0.0) @HasMaxDouble(100.0) required double score,
@StartsWith('https://') String? website,
@EndsWith('.com') String? domain,
@Contains('test') String? testField,
@IsUuid() required String uuid,
}) = _AnnotatedModel;

factory AnnotatedModel.fromJson(Map<String, dynamic> json) =>
_$AnnotatedModelFromJson(json);
}
Loading