Files
buzz/mobile/test/shared/widgets/skeleton_test.dart
T
cls 9dfa06ffee
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
feat: import Chinese-localized Buzz source snapshot
Signed-off-by: cls_宁波本机 <908705107@qq.com>
2026-08-13 18:34:25 +08:00

187 lines
5.6 KiB
Dart

import 'package:buzz/shared/theme/theme.dart';
import 'package:buzz/shared/widgets/skeleton.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
Widget buildShimmerTestable({
bool disableAnimations = false,
bool enabled = true,
}) {
return MaterialApp(
theme: AppTheme.light(),
builder: (context, child) => MediaQuery(
data: MediaQuery.of(
context,
).copyWith(disableAnimations: disableAnimations),
child: child!,
),
home: Scaffold(
body: SkeletonShimmer(
enabled: enabled,
child: const SkeletonBar(width: 120, height: 16),
),
),
);
}
Widget buildRevealTestable({
required ValueNotifier<bool> loading,
bool disableAnimations = false,
}) {
return MaterialApp(
theme: AppTheme.light(),
builder: (context, child) => MediaQuery(
data: MediaQuery.of(
context,
).copyWith(disableAnimations: disableAnimations),
child: child!,
),
home: Scaffold(
body: ValueListenableBuilder(
valueListenable: loading,
builder: (context, isLoading, _) => SkeletonReveal(
loading: isLoading,
skeleton: const SkeletonBar(width: 120, height: 16),
content: const Text('Loaded content'),
),
),
),
);
}
double layerOpacity(WidgetTester tester, String key) =>
tester.widget<Opacity>(find.byKey(Key(key))).opacity;
bool contentFocusExcluded(WidgetTester tester) => tester
.widget<ExcludeFocus>(
find.descendant(
of: find.byKey(const Key('skeleton-reveal-content')),
matching: find.byType(ExcludeFocus),
),
)
.excluding;
testWidgets('sweeps a highlight across skeleton elements while loading', (
tester,
) async {
await tester.pumpWidget(buildShimmerTestable());
final shimmer = find.byType(SkeletonShimmer);
expect(find.byType(SkeletonBar), findsOneWidget);
expect(
find.descendant(of: shimmer, matching: find.byType(ShaderMask)),
findsOneWidget,
);
final boundary = tester.renderObject<RenderRepaintBoundary>(
find
.descendant(of: shimmer, matching: find.byType(RepaintBoundary))
.first,
);
final beforeBytes = await tester.runAsync(() async {
final image = await boundary.toImage();
return image.toByteData();
});
await tester.pump(const Duration(milliseconds: 1000));
final afterBytes = await tester.runAsync(() async {
final image = await boundary.toImage();
return image.toByteData();
});
expect(
beforeBytes!.buffer.asUint8List(),
isNot(equals(afterBytes!.buffer.asUint8List())),
);
});
testWidgets('keeps skeleton elements static for reduced motion', (
tester,
) async {
await tester.pumpWidget(buildShimmerTestable(disableAnimations: true));
final shimmer = find.byType(SkeletonShimmer);
expect(find.byType(SkeletonBar), findsOneWidget);
expect(
find.descendant(of: shimmer, matching: find.byType(ShaderMask)),
findsNothing,
);
});
testWidgets('keeps skeleton elements static when shimmer is disabled', (
tester,
) async {
await tester.pumpWidget(buildShimmerTestable(enabled: false));
final shimmer = find.byType(SkeletonShimmer);
expect(find.byType(SkeletonBar), findsOneWidget);
expect(
find.descendant(of: shimmer, matching: find.byType(ShaderMask)),
findsNothing,
);
});
testWidgets('reveals content in the same slot with a 400ms cross-fade', (
tester,
) async {
final loading = ValueNotifier(true);
await tester.pumpWidget(buildRevealTestable(loading: loading));
expect(layerOpacity(tester, 'skeleton-reveal-placeholder'), 1);
expect(layerOpacity(tester, 'skeleton-reveal-content'), 0);
expect(contentFocusExcluded(tester), isTrue);
loading.value = false;
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
expect(
layerOpacity(tester, 'skeleton-reveal-placeholder'),
closeTo(0.5, 0.01),
);
expect(layerOpacity(tester, 'skeleton-reveal-content'), closeTo(0.5, 0.01));
expect(contentFocusExcluded(tester), isTrue);
await tester.pump(const Duration(milliseconds: 200));
expect(layerOpacity(tester, 'skeleton-reveal-placeholder'), 0);
expect(layerOpacity(tester, 'skeleton-reveal-content'), 1);
expect(contentFocusExcluded(tester), isFalse);
expect(find.text('Loaded content'), findsOneWidget);
});
testWidgets('resets to the skeleton without animating backwards', (
tester,
) async {
final loading = ValueNotifier(false);
await tester.pumpWidget(buildRevealTestable(loading: loading));
expect(layerOpacity(tester, 'skeleton-reveal-content'), 1);
loading.value = true;
await tester.pump();
expect(layerOpacity(tester, 'skeleton-reveal-placeholder'), 1);
expect(layerOpacity(tester, 'skeleton-reveal-content'), 0);
});
testWidgets('reveals immediately for reduced motion', (tester) async {
final loading = ValueNotifier(true);
await tester.pumpWidget(
buildRevealTestable(loading: loading, disableAnimations: true),
);
final reveal = find.byType(SkeletonReveal);
expect(
find.descendant(of: reveal, matching: find.byType(ShaderMask)),
findsNothing,
);
loading.value = false;
await tester.pump();
expect(layerOpacity(tester, 'skeleton-reveal-placeholder'), 0);
expect(layerOpacity(tester, 'skeleton-reveal-content'), 1);
});
}