| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'dart:typed_data'; | |
| import 'dart:ui' as ui; | |
| import 'package:flutter_driver/driver_extension.dart'; | |
| import 'windows.dart'; | |
| void drawHelloWorld(ui.FlutterView view) { | |
| final style = ui.ParagraphStyle(); | |
| final paragraphBuilder = ui.ParagraphBuilder(style)..addText('Hello world'); | |
| final ui.Paragraph paragraph = paragraphBuilder.build(); | |
| paragraph.layout(const ui.ParagraphConstraints(width: 100.0)); | |
| final recorder = ui.PictureRecorder(); | |
| final canvas = ui.Canvas(recorder); | |
| canvas.drawParagraph(paragraph, ui.Offset.zero); | |
| final ui.Picture picture = recorder.endRecording(); | |
| final sceneBuilder = ui.SceneBuilder() | |
| ..addPicture(ui.Offset.zero, picture) | |
| ..pop(); | |
| view.render(sceneBuilder.build()); | |
| } | |
| Future<void> _waitUntilWindowVisible() async { | |
| while (!await isWindowVisible()) { | |
| await Future<void>.delayed(const Duration(milliseconds: 100)); | |
| } | |
| } | |
| void _expectVisible(bool current, bool expect, Completer<String> completer, int frameCount) { | |
| if (current != expect) { | |
| try { | |
| throw 'Window should be ${expect ? 'visible' : 'hidden'} on frame $frameCount'; | |
| } catch (e) { | |
| if (!completer.isCompleted) { | |
| completer.completeError(e); | |
| } | |
| rethrow; | |
| } | |
| } | |
| } | |
| void main() async { | |
| // TODO(goderbauer): Create a window if embedder doesn't provide an implicit view to draw into. | |
| assert(ui.PlatformDispatcher.instance.implicitView != null); | |
| final ui.FlutterView view = ui.PlatformDispatcher.instance.implicitView!; | |
| // Create a completer to send the window visibility result back to the | |
| // integration test. | |
| final visibilityCompleter = Completer<String>(); | |
| enableFlutterDriverExtension( | |
| handler: (String? message) async { | |
| if (message == 'verifyWindowVisibility') { | |
| return visibilityCompleter.future; | |
| } else if (message == 'verifyTheme') { | |
| final bool app = await isAppDarkModeEnabled(); | |
| final bool system = await isSystemDarkModeEnabled(); | |
| return (app == system) | |
| ? 'success' | |
| : 'error: app dark mode ($app) does not match system dark mode ($system)'; | |
| } else if (message == 'verifyStringConversion') { | |
| // Use a test string that contains code points that fit in both 8 and 16 bits. | |
| // The code points are passed a list of integers through the method channel, | |
| // which will use the UTF16 to UTF8 utility function to convert them to a | |
| // std::string, which should equate to the original expected string. | |
| const expected = 'ABCℵ'; | |
| final codePoints = Int32List.fromList(expected.codeUnits); | |
| final String converted = await testStringConversion(codePoints); | |
| return (converted == expected) | |
| ? 'success' | |
| : 'error: conversion of UTF16 string to UTF8 failed, expected "${expected.codeUnits}" but got "${converted.codeUnits}"'; | |
| } else if (message == 'verifyNullStringConversion') { | |
| // Test that Utf8FromUtf16 handles nullptr gracefully by returning empty string. | |
| final String converted = await testNullStringConversion(); | |
| return converted.isEmpty | |
| ? 'success' | |
| : 'error: nullptr conversion should return empty string, got "${converted.codeUnits}"'; | |
| } else if (message == 'verifyEmptyStringConversion') { | |
| // Test that Utf8FromUtf16 handles empty string gracefully. | |
| final String converted = await testEmptyStringConversion(); | |
| return converted.isEmpty | |
| ? 'success' | |
| : 'error: empty string conversion should return empty string, got "${converted.codeUnits}"'; | |
| } else if (message == 'verifyInvalidUtf16Conversion') { | |
| // Test that Utf8FromUtf16 handles invalid UTF-16 (unpaired surrogate) gracefully. | |
| // With WC_ERR_INVALID_CHARS flag, WideCharToMultiByte returns 0 for invalid input, | |
| // so Utf8FromUtf16 should return an empty string. | |
| final String converted = await testInvalidUtf16Conversion(); | |
| return converted.isEmpty | |
| ? 'success' | |
| : 'error: invalid UTF-16 conversion should return empty string, got "${converted.codeUnits}"'; | |
| } | |
| throw 'Unrecognized message: $message'; | |
| }, | |
| ); | |
| try { | |
| if (await isWindowVisible()) { | |
| throw 'Window should be hidden at startup'; | |
| } | |
| var frameCount = 0; | |
| ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) { | |
| // Our goal is to verify that it's `drawHelloWorld` that makes the window | |
| // appear, not anything else. This requires checking the visibility right | |
| // before drawing, but since `isWindowVisible` has to be async, and | |
| // `FlutterView.render` (in `drawHelloWorld`) forbids async before it, | |
| // this can not be done during a single onBeginFrame. However, we can | |
| // verify in separate frames to indirectly prove it, by ensuring that | |
| // no other mechanism can affect isWindowVisible in the first frame at all. | |
| frameCount += 1; | |
| switch (frameCount) { | |
| // The 1st frame: render nothing, just verify that the window is hidden. | |
| case 1: | |
| isWindowVisible().then((bool visible) { | |
| _expectVisible(visible, false, visibilityCompleter, frameCount); | |
| ui.PlatformDispatcher.instance.scheduleFrame(); | |
| }); | |
| // The 2nd frame: render, which makes the window appear. | |
| case 2: | |
| drawHelloWorld(view); | |
| _waitUntilWindowVisible().then((_) { | |
| if (!visibilityCompleter.isCompleted) { | |
| visibilityCompleter.complete('success'); | |
| } | |
| }); | |
| // Others, in case requested to render. | |
| default: | |
| drawHelloWorld(view); | |
| } | |
| }; | |
| } catch (e) { | |
| visibilityCompleter.completeError(e); | |
| rethrow; | |
| } | |
| ui.PlatformDispatcher.instance.scheduleFrame(); | |
| } |