Prechádzať zdrojové kódy

feat: tested controller layer

Fela Maslen 4 rokov pred
rodič
commit
cc942d172d

+ 133 - 0
gmus-mobile/pubspec.lock

@@ -1,6 +1,27 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "14.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.41.2"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
   async:
     dependency: transitive
     description:
@@ -15,6 +36,27 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0"
+  build:
+    dependency: transitive
+    description:
+      name: build
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.6.2"
+  built_collection:
+    dependency: transitive
+    description:
+      name: built_collection
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "5.0.0"
+  built_value:
+    dependency: transitive
+    description:
+      name: built_value
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "8.0.0"
   characters:
     dependency: transitive
     description:
@@ -29,6 +71,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.0"
+  cli_util:
+    dependency: transitive
+    description:
+      name: cli_util
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.3.0"
   clock:
     dependency: transitive
     description:
@@ -36,6 +85,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.1.0"
+  code_builder:
+    dependency: transitive
+    description:
+      name: code_builder
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.6.0"
   collection:
     dependency: transitive
     description:
@@ -64,6 +120,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.0.0"
+  dart_style:
+    dependency: transitive
+    description:
+      name: dart_style
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.12"
   fake_async:
     dependency: transitive
     description:
@@ -71,6 +134,20 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.0"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.0.0"
+  fixnum:
+    dependency: transitive
+    description:
+      name: fixnum
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -95,6 +172,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "3.25.4"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
   http:
     dependency: "direct main"
     description:
@@ -109,6 +193,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "4.0.0"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
   matcher:
     dependency: transitive
     description:
@@ -123,6 +214,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.3.0"
+  mockito:
+    dependency: "direct main"
+    description:
+      name: mockito
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.1.4"
   nanoid:
     dependency: "direct main"
     description:
@@ -130,6 +228,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "0.1.0"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.9.3"
   path:
     dependency: transitive
     description:
@@ -144,11 +249,25 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.10.0"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
   sky_engine:
     dependency: transitive
     description: flutter
     source: sdk
     version: "0.0.99"
+  source_gen:
+    dependency: transitive
+    description:
+      name: source_gen
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.9.10+2"
   source_span:
     dependency: transitive
     description:
@@ -205,6 +324,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.1.0"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.0"
   web_socket_channel:
     dependency: "direct main"
     description:
@@ -212,5 +338,12 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "1.2.0"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.0"
 sdks:
   dart: ">=2.12.0-0.0 <3.0.0"

+ 2 - 1
gmus-mobile/pubspec.yaml

@@ -21,12 +21,13 @@ environment:
   sdk: ">=2.7.0 <3.0.0"
 
 dependencies:
+  cupertino_icons: ^1.0.0
   flutter:
     sdk: flutter
   flutter_dotenv:
   get:
   http:
-  cupertino_icons: ^1.0.0
+  mockito:
   nanoid:
   web_socket_channel:
 

+ 140 - 0
gmus-mobile/test/controller_test.dart

@@ -0,0 +1,140 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:gmus_mobile/actions.dart';
+import 'package:gmus_mobile/controller.dart';
+import 'package:mockito/mockito.dart';
+import 'package:web_socket_channel/io.dart';
+import 'package:web_socket_channel/web_socket_channel.dart';
+
+void main() {
+  group('Controller', () {
+    group('Receiving messages from pubsub', () {
+      group(CLIENT_LIST_UPDATED, () {
+        String message = '{"type":"CLIENT_LIST_UPDATED","payload":[{"name":"client-A","lastPing":123},{"name":"client-B","lastPing":456}]}';
+
+        test('client list should be updated', () {
+          Controller controller = Controller();
+
+          controller.onRemoteMessage(message);
+
+          expect(controller.clients.length, 2);
+
+          expect(controller.clients[0].name, 'client-A');
+          expect(controller.clients[0].lastPing, 123);
+
+          expect(controller.clients[1].name, 'client-B');
+          expect(controller.clients[1].lastPing, 456);
+        });
+      });
+
+      group(STATE_SET, () {
+        String message = '{"type":"STATE_SET","payload":{"master":"new-master-client","songId":7123,"currentTime":10.843,"seekTime":23.001,"playing":false,"queue":[9750]}}';
+
+        test('player state should be updated', () {
+          Controller controller = Controller();
+          controller.onRemoteMessage(message);
+
+          expect(controller.player.value.master, 'new-master-client');
+          expect(controller.player.value.songId, 7123);
+          expect(controller.player.value.currentTime, 10.843);
+          expect(controller.player.value.seekTime, 23.001);
+          expect(controller.player.value.playing, false);
+        });
+
+        test('queue should be updated', () {
+          Controller controller = Controller();
+          controller.onRemoteMessage(message);
+
+          expect(controller.player.value.queue.length, 1);
+          expect(controller.player.value.queue[0], 9750);
+        });
+      });
+    });
+
+    group('Play / pause', () {
+      group('When slave', () {
+        group('when paused', () {
+          test('should send a "play" action to pubsub', () {
+            var controller = mockControllerAsSlave();
+            controller.player.value.playing = false;
+
+            controller.playPause();
+
+            verify(controller.channel.sink.add('{"type":"STATE_SET","payload":{"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","songId":null,"playing":true,"queue":[]}}')).called(1);
+          });
+        });
+
+        group('when playing', () {
+          test('should send a "pause" action to pubsub', () {
+            var controller = mockControllerAsSlave();
+            controller.player.value.playing = true;
+            controller.player.value.songId = 182;
+
+            controller.playPause();
+
+            verify(controller.channel.sink.add('{"type":"STATE_SET","payload":{"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","songId":182,"playing":false,"queue":[]}}')).called(1);
+          });
+        });
+      });
+
+      group('When master', () {
+        test('should not send any actions to pubsub', () {
+          var controller = mockControllerAsMaster();
+          controller.player.value.playing = false;
+          controller.playPause();
+
+          verifyNever(controller.channel.sink.add('{"type":"STATE_SET","payload":{"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","songId":null,"playing":true,"queue":[]}}'));
+        });
+      });
+    });
+
+    group('Playing a song', () {
+      group('When slave', () {
+        test('should send an action to pubsub to play the song', () {
+          var controller = mockControllerAsSlave();
+
+          controller.playSong(871);
+
+          verify(controller.channel.sink.add('{"type":"STATE_SET","payload":{"currentTime":0.0,"seekTime":-1.0,"master":"other-client-name-master","songId":871,"playing":true,"queue":[]}}')).called(1);
+        });
+      });
+
+      group('When master', () {
+        test('should throw an error', () {
+          var controller = mockControllerAsMaster();
+
+          expect(() => controller.playSong(871), throwsException);
+        });
+      });
+    });
+  });
+}
+
+class MockWebSocketSink extends Mock implements WebSocketSink {}
+
+class MockChannel extends Mock implements IOWebSocketChannel {
+  final WebSocketSink sink;
+  MockChannel({
+    this.sink,
+  });
+}
+
+Controller mockControllerAsMaster() {
+  Controller controllerAsMaster = Controller();
+  controllerAsMaster.setUniqueName('my-client-name-master');
+  controllerAsMaster.player.value.master = 'my-client-name-master';
+
+  controllerAsMaster.channel = MockChannel(sink: MockWebSocketSink());
+
+  return controllerAsMaster;
+}
+
+Controller mockControllerAsSlave() {
+  Controller controllerAsSlave = Controller();
+  controllerAsSlave.setUniqueName('my-client-name-slave');
+  controllerAsSlave.player.value.master = 'other-client-name-master';
+
+  controllerAsSlave.channel = MockChannel(sink: MockWebSocketSink());
+
+  return controllerAsSlave;
+}
+