diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 570a8f1..893d621 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -36,7 +36,7 @@ Purpose: Provide just enough project context so an AI assistant can make correct ### 4. Testing Strategy * Unit tests in `test/unit/` mirror README examples & edge cases (list formats, depth, duplicates, charsets, null handling, custom hooks). * E2E tests in `test/e2e/` exercise higher‑level URI extensions. -* Comparison tests (`test/comparison/`) ensure parity with the JS implementation via fixture JSON & a Node script; run `test/comparison/compare_outputs.sh` after semantic changes to core encode/decode logic. +* Comparison tests (`test/comparison/`) ensure Dart VM and dart2js parity with the JS implementation via fixture JSON & a Node script; run `test/comparison/compare_outputs.sh` after semantic changes to core encode/decode logic. * When adding behavior, first add/modify a unit test replicating the JS `qs` behavior (consult upstream if uncertain), then adjust implementation. ### 5. Dev Workflow / Commands diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b80cce9..82f2654 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -237,16 +237,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Run a comparison test between qs_dart and qs for JavaScript working-directory: test/comparison - run: | - set -e - node_output=$(node qs.js) - dart_output=$(dart run qs.dart) - if [ "$node_output" == "$dart_output" ]; then - echo "The outputs are identical." - else - echo "The outputs are different." - exit 1 - fi + run: bash compare_outputs.sh ci_required: name: "CI Required" if: ${{ always() }} diff --git a/test/comparison/compare_outputs.sh b/test/comparison/compare_outputs.sh index ebe11b2..da73566 100755 --- a/test/comparison/compare_outputs.sh +++ b/test/comparison/compare_outputs.sh @@ -1,17 +1,33 @@ #!/usr/bin/env bash -# Get the directory of the script -script_dir=$(dirname "$0") - -# Run the JavaScript and Dart scripts and save their outputs -node_output=$(node "$script_dir/qs.js") -dart_output=$(dart run "$script_dir/qs.dart") - -# Compare the outputs -if [ "$node_output" == "$dart_output" ]; then - echo "The outputs are identical." - exit 0 -else - echo "The outputs are different." +set -euo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +tmp_dir=$(mktemp -d) +trap 'rm -rf "$tmp_dir"' EXIT + +compare_outputs() { + local expected="$1" + local actual="$2" + local description="$3" + + if ! diff -u "$expected" "$actual"; then + echo "$description outputs are different." >&2 exit 1 -fi + fi +} + +node "$script_dir/qs.js" > "$tmp_dir/node.out" +dart run "$script_dir/qs.dart" > "$tmp_dir/dart_vm.out" + +test_cases_base64=$(base64 < "$script_dir/test_cases.json" | tr -d '\r\n') +dart compile js -O2 \ + -DQS_COMPARISON_TEST_CASES_BASE64="$test_cases_base64" \ + "$script_dir/qs_dart2js.dart" \ + -o "$tmp_dir/qs_dart2js.js" +node "$tmp_dir/qs_dart2js.js" > "$tmp_dir/dart2js.out" + +compare_outputs "$tmp_dir/node.out" "$tmp_dir/dart_vm.out" "Node and Dart VM" +compare_outputs "$tmp_dir/node.out" "$tmp_dir/dart2js.out" "Node and dart2js" + +echo "The Node, Dart VM, and dart2js outputs are identical." diff --git a/test/comparison/comparison.dart b/test/comparison/comparison.dart new file mode 100644 index 0000000..c04a2e9 --- /dev/null +++ b/test/comparison/comparison.dart @@ -0,0 +1,15 @@ +import 'dart:convert' show jsonDecode, jsonEncode; + +import 'package:qs_dart/qs_dart.dart' as qs; + +Iterable buildComparisonLines(String testCasesJson) sync* { + final List> e2eTestCases = + List.from(jsonDecode(testCasesJson)); + + for (final Map testCase in e2eTestCases) { + final String encoded = qs.encode(testCase['data']); + final Map decoded = qs.decode(testCase['encoded']); + yield 'Encoded: $encoded'; + yield 'Decoded: ${jsonEncode(decoded)}'; + } +} diff --git a/test/comparison/package.json b/test/comparison/package.json index f6ebede..8ce423c 100644 --- a/test/comparison/package.json +++ b/test/comparison/package.json @@ -4,7 +4,7 @@ "description": "A comparison of query string parsing libraries", "author": "Klemen Tusar", "license": "BSD-3-Clause", - "packageManager": "pnpm@11.1.3", + "packageManager": "pnpm@11.5.2", "dependencies": { "qs": "^6.15.2" } diff --git a/test/comparison/qs.dart b/test/comparison/qs.dart index 3650f66..97c6055 100644 --- a/test/comparison/qs.dart +++ b/test/comparison/qs.dart @@ -1,9 +1,9 @@ -import 'dart:convert' show jsonDecode, jsonEncode; import 'dart:io' show File, Platform, exitCode, stdout; import 'package:cli_script/cli_script.dart' show wrapMain; import 'package:path/path.dart' as p; -import 'package:qs_dart/qs_dart.dart' as qs; + +import 'comparison.dart'; void main() { wrapMain(() { @@ -12,16 +12,9 @@ void main() { final String scriptDir = p.dirname(Platform.script.toFilePath()); final File file = File(p.join(scriptDir, 'test_cases.json')); final String contents = file.readAsStringSync(); - final List> e2eTestCases = - List>.from( - jsonDecode(contents), - ); - for (final testCase in e2eTestCases) { - final String encoded = qs.encode(testCase['data']); - final Map decoded = qs.decode(testCase['encoded']); - stdout.writeln('Encoded: $encoded'); - stdout.writeln('Decoded: ${jsonEncode(decoded)}'); + for (final String line in buildComparisonLines(contents)) { + stdout.writeln(line); } }); } diff --git a/test/comparison/qs_dart2js.dart b/test/comparison/qs_dart2js.dart new file mode 100644 index 0000000..90019ed --- /dev/null +++ b/test/comparison/qs_dart2js.dart @@ -0,0 +1,17 @@ +import 'dart:convert' show base64Decode, utf8; + +import 'comparison.dart'; + +void main() { + const String encodedTestCases = + String.fromEnvironment('QS_COMPARISON_TEST_CASES_BASE64'); + if (encodedTestCases.isEmpty) { + throw StateError('Missing QS_COMPARISON_TEST_CASES_BASE64.'); + } + + final String testCasesJson = utf8.decode(base64Decode(encodedTestCases)); + for (final String line in buildComparisonLines(testCasesJson)) { + // ignore: avoid_print + print(line); + } +}