Skip to content
Merged
32 changes: 32 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Documentation

on:
push:
branches: ["main"]
paths:
- "docs/**"
- "docs.json"
- ".github/workflows/docs.yml"
pull_request:
branches: ["main"]
paths:
- "docs/**"
- "docs.json"
- ".github/workflows/docs.yml"

jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install docs.page CLI
run: npm install -g @docs.page/cli

- name: Validate documentation
run: npx @docs.page/cli check
41 changes: 31 additions & 10 deletions .github/workflows/firebase-hosting-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,59 @@
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on Merge

on:
push:
branches:
- main
paths:
- "example/**"
- "demo/**"
- "packages/**"
- "melos.yaml"
- "pubspec.yaml"
- "pubspec.lock"

jobs:
test:
name: Test
uses: ./.github/workflows/test.yml
name: Test
uses: ./.github/workflows/test.yml

build_and_deploy:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
- uses: kuhnroyal/flutter-fvm-config-action@v2
id: fvm-config-action

- name: Install dependencies
run: flutter pub get
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}

- name: Align Melos SDK path
run: |
mkdir -p .fvm
ln -sfn "$FLUTTER_ROOT" .fvm/flutter_sdk

- name: Setup Melos
uses: bluefireteam/melos-action@v3

- name: Build Runner
run: dart run build_runner build --delete-conflicting-outputs
run: melos run build_runner:build

- name: Build SuperDeck assets
run: |
cd demo
dart run superdeck_cli:main build

- name: Deploy Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
with:
entryPoint: ./example
entryPoint: ./demo
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_SUPERDECK_DEV }}
channelId: live
Expand Down
38 changes: 30 additions & 8 deletions .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,58 @@
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
on:

on:
pull_request:
paths:
- "example/**"
- "demo/**"
- "packages/**"
- "melos.yaml"
- "pubspec.yaml"
- "pubspec.lock"

permissions:
checks: write
contents: read
pull-requests: write

jobs:
build_and_preview:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup Flutter
uses: subosito/flutter-action@v2
- uses: kuhnroyal/flutter-fvm-config-action@v2
id: fvm-config-action

- name: Install dependencies
run: flutter pub get
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ steps.fvm-config-action.outputs.FLUTTER_VERSION }}
channel: ${{ steps.fvm-config-action.outputs.FLUTTER_CHANNEL }}

- name: Align Melos SDK path
run: |
mkdir -p .fvm
ln -sfn "$FLUTTER_ROOT" .fvm/flutter_sdk

- name: Setup Melos
uses: bluefireteam/melos-action@v3

- name: Build Runner
run: dart run build_runner build --delete-conflicting-outputs
run: melos run build_runner:build

Comment on lines +40 to +45
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

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

This workflow no longer installs Dart/Flutter dependencies (e.g., flutter pub get or melos bootstrap) before running melos run build_runner:build. In this repo’s test.yml workflow dependencies are installed explicitly, so this job is likely to fail with missing packages. Add a dependency install step consistent with test.yml (or run melos bootstrap) before build_runner.

Copilot uses AI. Check for mistakes.
- name: Build SuperDeck assets
run: |
cd demo
dart run superdeck_cli:main build

- name: Deploy Firebase
uses: FirebaseExtended/action-hosting-deploy@v0
env:
FIREBASE_CLI_EXPERIMENTS: webframeworks
with:
entryPoint: ./example
entryPoint: ./demo
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_SUPERDECK_DEV }}
expires: 30d
Expand Down
55 changes: 45 additions & 10 deletions demo/integration_test/app_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ void main() {
final exception = tester.takeException();
if (exception != null) {
final isLayoutOverflow = exception.toString().contains('overflowed');
expect(isLayoutOverflow, isTrue,
reason: 'Only layout overflow is acceptable, got: $exception');
expect(
isLayoutOverflow,
isTrue,
reason: 'Only layout overflow is acceptable, got: $exception',
);
}
});

Expand Down Expand Up @@ -62,12 +65,28 @@ void main() {
testWidgets('slides load and display', (tester) async {
final controller = await tester.pumpTestApp();

expect(controller, isNotNull, reason: 'DeckController should be available');
expect(controller!.isLoading.value, isFalse, reason: 'Loading should complete');
expect(controller.hasError.value, isFalse, reason: 'No error should occur');
expect(
controller,
isNotNull,
reason: 'DeckController should be available',
);
expect(
controller!.isLoading.value,
isFalse,
reason: 'Loading should complete',
);
expect(
controller.hasError.value,
isFalse,
reason: 'No error should occur',
);

final slideCount = controller.totalSlides.value;
expect(slideCount, greaterThan(0), reason: 'Should have at least one slide');
expect(
slideCount,
greaterThan(0),
reason: 'Should have at least one slide',
);
});

testWidgets('demo app has at least 5 slides', (tester) async {
Expand All @@ -85,7 +104,11 @@ void main() {
final controller = await tester.pumpTestApp();

expect(controller, isNotNull);
expect(controller!.currentIndex.value, 0, reason: 'Should start at first slide');
expect(
controller!.currentIndex.value,
0,
reason: 'Should start at first slide',
);
expect(
controller.currentSlide.value,
isNotNull,
Expand All @@ -100,13 +123,21 @@ void main() {

expect(controller, isNotNull);
expect(controller!.currentIndex.value, 0);
expect(controller.canGoNext.value, isTrue, reason: 'Should be able to go next');
expect(
controller.canGoNext.value,
isTrue,
reason: 'Should be able to go next',
);

// Navigate to next slide
await controller.nextSlide();
await tester.pumpAndSettle(const Duration(seconds: 2));

expect(controller.currentIndex.value, 1, reason: 'Should be on second slide');
expect(
controller.currentIndex.value,
1,
reason: 'Should be on second slide',
);
});

testWidgets('can navigate to previous slide', (tester) async {
Expand All @@ -123,7 +154,11 @@ void main() {
await controller.previousSlide();
await tester.pumpAndSettle(const Duration(seconds: 2));

expect(controller.currentIndex.value, 0, reason: 'Should be back on first slide');
expect(
controller.currentIndex.value,
0,
reason: 'Should be back on first slide',
);
});

testWidgets('canGoPrevious is false on first slide', (tester) async {
Expand Down
5 changes: 1 addition & 4 deletions demo/integration_test/helpers/test_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ class TestApp extends StatelessWidget {
options: DeckOptions(
baseStyle: borderedStyle(),
widgets: demoWidgets,
styles: {
'announcement': announcementStyle(),
'quote': quoteStyle(),
},
styles: {'announcement': announcementStyle(), 'quote': quoteStyle()},
parts: const SlideParts(
header: HeaderPart(),
footer: FooterPart(),
Expand Down
3 changes: 1 addition & 2 deletions demo/lib/src/parts/header.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ class HeaderPart extends StatelessWidget implements PreferredSizeWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (slide.options.title != null)
Text(slide.options.title!),
if (slide.options.title != null) Text(slide.options.title!),
const Spacer(),
Text('${index + 1}'),
],
Expand Down
68 changes: 34 additions & 34 deletions demo/lib/src/widgets/demo_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,51 +22,51 @@ import '../examples/button.dart' as remix_button;
///
/// The QR code widget is now a built-in widget available as `@qrcode`.
Map<String, WidgetDefinition> get demoWidgets => {
// Mix examples - wrapped in simple widget definitions
'mix-simple-box': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(scale: 3.0, child: mix_simple_box.Example()),
),
),
'mix-variants': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(scale: 3.0, child: mix_variants.Example()),
),
),
'mix-animation': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(
scale: 3.0,
child: mix_animation.SwitchAnimation(),
),
),
// Mix examples - wrapped in simple widget definitions
'mix-simple-box': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(scale: 3.0, child: mix_simple_box.Example()),
),
),
'mix-variants': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(scale: 3.0, child: mix_variants.Example()),
),
),
'mix-animation': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(
scale: 3.0,
child: mix_animation.SwitchAnimation(),
),
),
),

// Naked UI examples
'naked-select': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(
scale: 2.0,
child: naked_select.SimpleSelectExample(),
),
),
// Naked UI examples
'naked-select': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(
scale: 2.0,
child: naked_select.SimpleSelectExample(),
),
),
),

// Remix examples
'remix-button': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(scale: 1.2, child: remix_button.ButtonExample()),
),
),
};
// Remix examples
'remix-button': _SimpleWidgetDefinition(
(context, args) => _DemoWrapper(
child: Transform.scale(scale: 1.2, child: remix_button.ButtonExample()),
),
),
};

/// Simple widget definition for widgets without schemas.
///
/// Used for demo widgets that don't need argument validation.
/// Uses raw `Map<String, Object?>` as the argument type (no parsing).
class _SimpleWidgetDefinition extends WidgetDefinition<Map<String, Object?>> {
final Widget Function(BuildContext context, Map<String, Object?> args)
_builder;
_builder;

const _SimpleWidgetDefinition(this._builder);

Expand Down
Loading
Loading