diff --git a/apps/flutter_parent/lib/models/course.dart b/apps/flutter_parent/lib/models/course.dart index 2be4d426bc..4753a567d8 100644 --- a/apps/flutter_parent/lib/models/course.dart +++ b/apps/flutter_parent/lib/models/course.dart @@ -16,6 +16,8 @@ library course; import 'package:built_collection/built_collection.dart'; import 'package:built_value/built_value.dart'; import 'package:built_value/serializer.dart'; +import 'package:flutter_parent/models/section.dart'; +import 'package:flutter_parent/models/term.dart'; import 'course_grade.dart'; import 'enrollment.dart'; @@ -63,11 +65,11 @@ abstract class Course implements Built { @nullable @BuiltValueField(wireName: 'start_at') - String get startAt; + DateTime get startAt; @nullable @BuiltValueField(wireName: 'end_at') - String get endAt; + DateTime get endAt; @nullable @BuiltValueField(wireName: 'syllabus_body') @@ -115,9 +117,16 @@ abstract class Course implements Built { @BuiltValueField(wireName: 'default_view') HomePage get homePage; + @nullable + Term get term; + + @nullable + BuiltList
get sections; + static void _initializeBuilder(CourseBuilder b) => b ..id = '' ..enrollments = ListBuilder() + ..sections = ListBuilder
() ..name = '' ..needsGradingCount = 0 ..hideFinalGrades = false @@ -158,6 +167,51 @@ abstract class Course implements Built { ); String contextFilterId() => 'course_${this.id}'; + + /// Verifies that the course should be displayed within the parent app + bool isValidForDate() { + if (accessRestrictedByDate) return false; + + if (workflowState == 'completed') return false; + + final now = DateTime.now(); + final isWithinCourseDates = _isWithinDates(startAt, endAt, now); + + if (restrictEnrollmentsToCourseDates) { + return isWithinCourseDates; + } else { + final isWithinTermDates = _isWithinDates(term?.startAt, term?.endAt, now); + var isWithinAnySection; + if (sections == null || sections.isEmpty) + isWithinAnySection = true; + else + isWithinAnySection = sections.any((section) => _isWithinDates(section.startAt, section.endAt, now)); + + return isWithinCourseDates && isWithinTermDates && isWithinAnySection; + } + } + + /// Filters enrollments by those associated with the currently selected user and isValidForParent + bool isValidForCurrentStudent(String currentStudentId) { + final hasValidEnrollments = enrollments?.any((enrollment) => enrollment.userId == currentStudentId) ?? false; + return hasValidEnrollments && isValidForDate(); + } + + bool _isWithinDates(DateTime startAt, DateTime endAt, DateTime now) { + bool isValidEndAt, isValidStartAt; + // If the dates are null, we have to show it + if (startAt == null) + isValidStartAt = true; + else + isValidStartAt = now.isAfter(startAt); + + if (endAt == null) + isValidEndAt = true; + else + isValidEndAt = now.isBefore(endAt); + + return isValidEndAt && isValidStartAt; + } } @BuiltValueEnum(wireName: 'default_view') diff --git a/apps/flutter_parent/lib/models/course.g.dart b/apps/flutter_parent/lib/models/course.g.dart index 924f130560..9efff7de24 100644 --- a/apps/flutter_parent/lib/models/course.g.dart +++ b/apps/flutter_parent/lib/models/course.g.dart @@ -106,14 +106,14 @@ class _$CourseSerializer implements StructuredSerializer { result.add(null); } else { result.add(serializers.serialize(object.startAt, - specifiedType: const FullType(String))); + specifiedType: const FullType(DateTime))); } result.add('end_at'); if (object.endAt == null) { result.add(null); } else { result.add(serializers.serialize(object.endAt, - specifiedType: const FullType(String))); + specifiedType: const FullType(DateTime))); } result.add('syllabus_body'); if (object.syllabusBody == null) { @@ -143,6 +143,21 @@ class _$CourseSerializer implements StructuredSerializer { result.add(serializers.serialize(object.homePage, specifiedType: const FullType(HomePage))); } + result.add('term'); + if (object.term == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.term, + specifiedType: const FullType(Term))); + } + result.add('sections'); + if (object.sections == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.sections, + specifiedType: + const FullType(BuiltList, const [const FullType(Section)]))); + } return result; } @@ -176,11 +191,11 @@ class _$CourseSerializer implements StructuredSerializer { break; case 'start_at': result.startAt = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(DateTime)) as DateTime; break; case 'end_at': result.endAt = serializers.deserialize(value, - specifiedType: const FullType(String)) as String; + specifiedType: const FullType(DateTime)) as DateTime; break; case 'syllabus_body': result.syllabusBody = serializers.deserialize(value, @@ -240,6 +255,16 @@ class _$CourseSerializer implements StructuredSerializer { result.homePage = serializers.deserialize(value, specifiedType: const FullType(HomePage)) as HomePage; break; + case 'term': + result.term.replace(serializers.deserialize(value, + specifiedType: const FullType(Term)) as Term); + break; + case 'sections': + result.sections.replace(serializers.deserialize(value, + specifiedType: const FullType( + BuiltList, const [const FullType(Section)])) + as BuiltList); + break; } } @@ -282,9 +307,9 @@ class _$Course extends Course { @override final String courseCode; @override - final String startAt; + final DateTime startAt; @override - final String endAt; + final DateTime endAt; @override final String syllabusBody; @override @@ -313,6 +338,10 @@ class _$Course extends Course { final String workflowState; @override final HomePage homePage; + @override + final Term term; + @override + final BuiltList
sections; factory _$Course([void Function(CourseBuilder) updates]) => (new CourseBuilder()..update(updates)).build(); @@ -341,7 +370,9 @@ class _$Course extends Course { this.hasGradingPeriods, this.restrictEnrollmentsToCourseDates, this.workflowState, - this.homePage}) + this.homePage, + this.term, + this.sections}) : super._() { if (id == null) { throw new BuiltValueNullFieldError('Course', 'id'); @@ -418,7 +449,9 @@ class _$Course extends Course { restrictEnrollmentsToCourseDates == other.restrictEnrollmentsToCourseDates && workflowState == other.workflowState && - homePage == other.homePage; + homePage == other.homePage && + term == other.term && + sections == other.sections; } @override @@ -441,26 +474,26 @@ class _$Course extends Course { $jc( $jc( $jc( - $jc($jc($jc($jc($jc($jc(0, currentScore.hashCode), finalScore.hashCode), currentGrade.hashCode), finalGrade.hashCode), id.hashCode), - name.hashCode), - originalName.hashCode), - courseCode.hashCode), - startAt.hashCode), - endAt.hashCode), - syllabusBody.hashCode), - hideFinalGrades.hashCode), - isPublic.hashCode), - enrollments.hashCode), - needsGradingCount.hashCode), - applyAssignmentGroupWeights.hashCode), - isFavorite.hashCode), - accessRestrictedByDate.hashCode), - imageDownloadUrl.hashCode), - hasWeightedGradingPeriods.hashCode), - hasGradingPeriods.hashCode), - restrictEnrollmentsToCourseDates.hashCode), - workflowState.hashCode), - homePage.hashCode)); + $jc($jc($jc($jc($jc($jc($jc($jc(0, currentScore.hashCode), finalScore.hashCode), currentGrade.hashCode), finalGrade.hashCode), id.hashCode), name.hashCode), originalName.hashCode), + courseCode.hashCode), + startAt.hashCode), + endAt.hashCode), + syllabusBody.hashCode), + hideFinalGrades.hashCode), + isPublic.hashCode), + enrollments.hashCode), + needsGradingCount.hashCode), + applyAssignmentGroupWeights.hashCode), + isFavorite.hashCode), + accessRestrictedByDate.hashCode), + imageDownloadUrl.hashCode), + hasWeightedGradingPeriods.hashCode), + hasGradingPeriods.hashCode), + restrictEnrollmentsToCourseDates.hashCode), + workflowState.hashCode), + homePage.hashCode), + term.hashCode), + sections.hashCode)); } @override @@ -490,7 +523,9 @@ class _$Course extends Course { ..add('restrictEnrollmentsToCourseDates', restrictEnrollmentsToCourseDates) ..add('workflowState', workflowState) - ..add('homePage', homePage)) + ..add('homePage', homePage) + ..add('term', term) + ..add('sections', sections)) .toString(); } } @@ -530,13 +565,13 @@ class CourseBuilder implements Builder { String get courseCode => _$this._courseCode; set courseCode(String courseCode) => _$this._courseCode = courseCode; - String _startAt; - String get startAt => _$this._startAt; - set startAt(String startAt) => _$this._startAt = startAt; + DateTime _startAt; + DateTime get startAt => _$this._startAt; + set startAt(DateTime startAt) => _$this._startAt = startAt; - String _endAt; - String get endAt => _$this._endAt; - set endAt(String endAt) => _$this._endAt = endAt; + DateTime _endAt; + DateTime get endAt => _$this._endAt; + set endAt(DateTime endAt) => _$this._endAt = endAt; String _syllabusBody; String get syllabusBody => _$this._syllabusBody; @@ -607,6 +642,15 @@ class CourseBuilder implements Builder { HomePage get homePage => _$this._homePage; set homePage(HomePage homePage) => _$this._homePage = homePage; + TermBuilder _term; + TermBuilder get term => _$this._term ??= new TermBuilder(); + set term(TermBuilder term) => _$this._term = term; + + ListBuilder
_sections; + ListBuilder
get sections => + _$this._sections ??= new ListBuilder
(); + set sections(ListBuilder
sections) => _$this._sections = sections; + CourseBuilder() { Course._initializeBuilder(this); } @@ -637,6 +681,8 @@ class CourseBuilder implements Builder { _restrictEnrollmentsToCourseDates = _$v.restrictEnrollmentsToCourseDates; _workflowState = _$v.workflowState; _homePage = _$v.homePage; + _term = _$v.term?.toBuilder(); + _sections = _$v.sections?.toBuilder(); _$v = null; } return this; @@ -685,12 +731,19 @@ class CourseBuilder implements Builder { restrictEnrollmentsToCourseDates: restrictEnrollmentsToCourseDates, workflowState: workflowState, - homePage: homePage); + homePage: homePage, + term: _term?.build(), + sections: _sections?.build()); } catch (_) { String _$failedField; try { _$failedField = 'enrollments'; enrollments.build(); + + _$failedField = 'term'; + _term?.build(); + _$failedField = 'sections'; + _sections?.build(); } catch (e) { throw new BuiltValueNestedFieldError( 'Course', _$failedField, e.toString()); diff --git a/apps/flutter_parent/lib/models/section.dart b/apps/flutter_parent/lib/models/section.dart new file mode 100644 index 0000000000..93ca8ea030 --- /dev/null +++ b/apps/flutter_parent/lib/models/section.dart @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +part 'section.g.dart'; + +/// To have this built_value be generated, run this command from the project root: +/// flutter pub run build_runner build --delete-conflicting-outputs +abstract class Section implements Built { + @BuiltValueSerializer(serializeNulls: true) // Add this line to get nulls to serialize when we convert to JSON + static Serializer
get serializer => _$sectionSerializer; + + String get id; + String get name; + + @BuiltValueField(wireName: 'start_at') + @nullable + DateTime get startAt; + + @BuiltValueField(wireName: 'end_at') + @nullable + DateTime get endAt; + + Section._(); + factory Section([void Function(SectionBuilder) updates]) = _$Section; + + static void _initializeBuilder(SectionBuilder b) => b + ..id = '' + ..name = ''; +} diff --git a/apps/flutter_parent/lib/models/section.g.dart b/apps/flutter_parent/lib/models/section.g.dart new file mode 100644 index 0000000000..791df9862d --- /dev/null +++ b/apps/flutter_parent/lib/models/section.g.dart @@ -0,0 +1,191 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'section.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer
_$sectionSerializer = new _$SectionSerializer(); + +class _$SectionSerializer implements StructuredSerializer
{ + @override + final Iterable types = const [Section, _$Section]; + @override + final String wireName = 'Section'; + + @override + Iterable serialize(Serializers serializers, Section object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'id', + serializers.serialize(object.id, specifiedType: const FullType(String)), + 'name', + serializers.serialize(object.name, specifiedType: const FullType(String)), + ]; + result.add('start_at'); + if (object.startAt == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.startAt, + specifiedType: const FullType(DateTime))); + } + result.add('end_at'); + if (object.endAt == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.endAt, + specifiedType: const FullType(DateTime))); + } + return result; + } + + @override + Section deserialize(Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = new SectionBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current as String; + iterator.moveNext(); + final dynamic value = iterator.current; + if (value == null) continue; + switch (key) { + case 'id': + result.id = serializers.deserialize(value, + specifiedType: const FullType(String)) as String; + break; + case 'name': + result.name = serializers.deserialize(value, + specifiedType: const FullType(String)) as String; + break; + case 'start_at': + result.startAt = serializers.deserialize(value, + specifiedType: const FullType(DateTime)) as DateTime; + break; + case 'end_at': + result.endAt = serializers.deserialize(value, + specifiedType: const FullType(DateTime)) as DateTime; + break; + } + } + + return result.build(); + } +} + +class _$Section extends Section { + @override + final String id; + @override + final String name; + @override + final DateTime startAt; + @override + final DateTime endAt; + + factory _$Section([void Function(SectionBuilder) updates]) => + (new SectionBuilder()..update(updates)).build(); + + _$Section._({this.id, this.name, this.startAt, this.endAt}) : super._() { + if (id == null) { + throw new BuiltValueNullFieldError('Section', 'id'); + } + if (name == null) { + throw new BuiltValueNullFieldError('Section', 'name'); + } + } + + @override + Section rebuild(void Function(SectionBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + SectionBuilder toBuilder() => new SectionBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Section && + id == other.id && + name == other.name && + startAt == other.startAt && + endAt == other.endAt; + } + + @override + int get hashCode { + return $jf($jc( + $jc($jc($jc(0, id.hashCode), name.hashCode), startAt.hashCode), + endAt.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('Section') + ..add('id', id) + ..add('name', name) + ..add('startAt', startAt) + ..add('endAt', endAt)) + .toString(); + } +} + +class SectionBuilder implements Builder { + _$Section _$v; + + String _id; + String get id => _$this._id; + set id(String id) => _$this._id = id; + + String _name; + String get name => _$this._name; + set name(String name) => _$this._name = name; + + DateTime _startAt; + DateTime get startAt => _$this._startAt; + set startAt(DateTime startAt) => _$this._startAt = startAt; + + DateTime _endAt; + DateTime get endAt => _$this._endAt; + set endAt(DateTime endAt) => _$this._endAt = endAt; + + SectionBuilder() { + Section._initializeBuilder(this); + } + + SectionBuilder get _$this { + if (_$v != null) { + _id = _$v.id; + _name = _$v.name; + _startAt = _$v.startAt; + _endAt = _$v.endAt; + _$v = null; + } + return this; + } + + @override + void replace(Section other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$Section; + } + + @override + void update(void Function(SectionBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$Section build() { + final _$result = _$v ?? + new _$Section._(id: id, name: name, startAt: startAt, endAt: endAt); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/apps/flutter_parent/lib/models/serializers.dart b/apps/flutter_parent/lib/models/serializers.dart index fdb0ebc125..5a34377386 100644 --- a/apps/flutter_parent/lib/models/serializers.dart +++ b/apps/flutter_parent/lib/models/serializers.dart @@ -60,7 +60,9 @@ import 'package:flutter_parent/models/reminder.dart'; import 'package:flutter_parent/models/remote_file.dart'; import 'package:flutter_parent/models/schedule_item.dart'; import 'package:flutter_parent/models/school_domain.dart'; +import 'package:flutter_parent/models/section.dart'; import 'package:flutter_parent/models/submission.dart'; +import 'package:flutter_parent/models/term.dart'; import 'package:flutter_parent/models/terms_of_service.dart'; import 'package:flutter_parent/models/unread_count.dart'; import 'package:flutter_parent/models/user.dart'; @@ -140,11 +142,13 @@ part 'serializers.g.dart'; Recipient, Reminder, RemoteFile, + Section, SchoolDomain, ScheduleItem, SeedContext, SeededUser, Submission, + Term, TermsOfService, UnreadCount, User, diff --git a/apps/flutter_parent/lib/models/serializers.g.dart b/apps/flutter_parent/lib/models/serializers.g.dart index 364b42b7f1..d6ba3bb85e 100644 --- a/apps/flutter_parent/lib/models/serializers.g.dart +++ b/apps/flutter_parent/lib/models/serializers.g.dart @@ -70,10 +70,12 @@ Serializers _$_serializers = (new Serializers().toBuilder() ..add(RemoteFile.serializer) ..add(ScheduleItem.serializer) ..add(SchoolDomain.serializer) + ..add(Section.serializer) ..add(SeedContext.serializer) ..add(SeededUser.serializer) ..add(Submission.serializer) ..add(SubmissionTypes.serializer) + ..add(Term.serializer) ..add(TermsOfService.serializer) ..add(UnreadCount.serializer) ..add(User.serializer) @@ -100,6 +102,9 @@ Serializers _$_serializers = (new Serializers().toBuilder() ..addBuilderFactory( const FullType(BuiltList, const [const FullType(Enrollment)]), () => new ListBuilder()) + ..addBuilderFactory( + const FullType(BuiltList, const [const FullType(Section)]), + () => new ListBuilder
()) ..addBuilderFactory( const FullType(BuiltList, const [const FullType(GradingPeriod)]), () => new ListBuilder()) diff --git a/apps/flutter_parent/lib/models/term.dart b/apps/flutter_parent/lib/models/term.dart new file mode 100644 index 0000000000..724c9f35de --- /dev/null +++ b/apps/flutter_parent/lib/models/term.dart @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 - present Instructure, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import 'package:built_value/built_value.dart'; +import 'package:built_value/serializer.dart'; + +part 'term.g.dart'; + +/// To have this built_value be generated, run this command from the project root: +/// flutter pub run build_runner build --delete-conflicting-outputs +abstract class Term implements Built { + @BuiltValueSerializer(serializeNulls: true) // Add this line to get nulls to serialize when we convert to JSON + static Serializer get serializer => _$termSerializer; + + String get id; + + @nullable + String get name; + + @BuiltValueField(wireName: 'start_at') + @nullable + DateTime get startAt; + + @BuiltValueField(wireName: 'end_at') + @nullable + DateTime get endAt; + + Term._(); + factory Term([void Function(TermBuilder) updates]) = _$Term; + + static void _initializeBuilder(TermBuilder b) => b..id = ''; +} diff --git a/apps/flutter_parent/lib/models/term.g.dart b/apps/flutter_parent/lib/models/term.g.dart new file mode 100644 index 0000000000..a6f48e8df3 --- /dev/null +++ b/apps/flutter_parent/lib/models/term.g.dart @@ -0,0 +1,193 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'term.dart'; + +// ************************************************************************** +// BuiltValueGenerator +// ************************************************************************** + +Serializer _$termSerializer = new _$TermSerializer(); + +class _$TermSerializer implements StructuredSerializer { + @override + final Iterable types = const [Term, _$Term]; + @override + final String wireName = 'Term'; + + @override + Iterable serialize(Serializers serializers, Term object, + {FullType specifiedType = FullType.unspecified}) { + final result = [ + 'id', + serializers.serialize(object.id, specifiedType: const FullType(String)), + ]; + result.add('name'); + if (object.name == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.name, + specifiedType: const FullType(String))); + } + result.add('start_at'); + if (object.startAt == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.startAt, + specifiedType: const FullType(DateTime))); + } + result.add('end_at'); + if (object.endAt == null) { + result.add(null); + } else { + result.add(serializers.serialize(object.endAt, + specifiedType: const FullType(DateTime))); + } + return result; + } + + @override + Term deserialize(Serializers serializers, Iterable serialized, + {FullType specifiedType = FullType.unspecified}) { + final result = new TermBuilder(); + + final iterator = serialized.iterator; + while (iterator.moveNext()) { + final key = iterator.current as String; + iterator.moveNext(); + final dynamic value = iterator.current; + if (value == null) continue; + switch (key) { + case 'id': + result.id = serializers.deserialize(value, + specifiedType: const FullType(String)) as String; + break; + case 'name': + result.name = serializers.deserialize(value, + specifiedType: const FullType(String)) as String; + break; + case 'start_at': + result.startAt = serializers.deserialize(value, + specifiedType: const FullType(DateTime)) as DateTime; + break; + case 'end_at': + result.endAt = serializers.deserialize(value, + specifiedType: const FullType(DateTime)) as DateTime; + break; + } + } + + return result.build(); + } +} + +class _$Term extends Term { + @override + final String id; + @override + final String name; + @override + final DateTime startAt; + @override + final DateTime endAt; + + factory _$Term([void Function(TermBuilder) updates]) => + (new TermBuilder()..update(updates)).build(); + + _$Term._({this.id, this.name, this.startAt, this.endAt}) : super._() { + if (id == null) { + throw new BuiltValueNullFieldError('Term', 'id'); + } + } + + @override + Term rebuild(void Function(TermBuilder) updates) => + (toBuilder()..update(updates)).build(); + + @override + TermBuilder toBuilder() => new TermBuilder()..replace(this); + + @override + bool operator ==(Object other) { + if (identical(other, this)) return true; + return other is Term && + id == other.id && + name == other.name && + startAt == other.startAt && + endAt == other.endAt; + } + + @override + int get hashCode { + return $jf($jc( + $jc($jc($jc(0, id.hashCode), name.hashCode), startAt.hashCode), + endAt.hashCode)); + } + + @override + String toString() { + return (newBuiltValueToStringHelper('Term') + ..add('id', id) + ..add('name', name) + ..add('startAt', startAt) + ..add('endAt', endAt)) + .toString(); + } +} + +class TermBuilder implements Builder { + _$Term _$v; + + String _id; + String get id => _$this._id; + set id(String id) => _$this._id = id; + + String _name; + String get name => _$this._name; + set name(String name) => _$this._name = name; + + DateTime _startAt; + DateTime get startAt => _$this._startAt; + set startAt(DateTime startAt) => _$this._startAt = startAt; + + DateTime _endAt; + DateTime get endAt => _$this._endAt; + set endAt(DateTime endAt) => _$this._endAt = endAt; + + TermBuilder() { + Term._initializeBuilder(this); + } + + TermBuilder get _$this { + if (_$v != null) { + _id = _$v.id; + _name = _$v.name; + _startAt = _$v.startAt; + _endAt = _$v.endAt; + _$v = null; + } + return this; + } + + @override + void replace(Term other) { + if (other == null) { + throw new ArgumentError.notNull('other'); + } + _$v = other as _$Term; + } + + @override + void update(void Function(TermBuilder) updates) { + if (updates != null) updates(this); + } + + @override + _$Term build() { + final _$result = + _$v ?? new _$Term._(id: id, name: name, startAt: startAt, endAt: endAt); + replace(_$result); + return _$result; + } +} + +// ignore_for_file: always_put_control_body_on_new_line,always_specify_types,annotate_overrides,avoid_annotating_with_dynamic,avoid_as,avoid_catches_without_on_clauses,avoid_returning_this,lines_longer_than_80_chars,omit_local_variable_types,prefer_expression_function_bodies,sort_constructors_first,test_types_in_equals,unnecessary_const,unnecessary_new diff --git a/apps/flutter_parent/lib/screens/calendar/calendar_widget/calendar_filter_screen/calendar_filter_list_interactor.dart b/apps/flutter_parent/lib/screens/calendar/calendar_widget/calendar_filter_screen/calendar_filter_list_interactor.dart index bef5877160..f1e8bf5407 100644 --- a/apps/flutter_parent/lib/screens/calendar/calendar_widget/calendar_filter_screen/calendar_filter_list_interactor.dart +++ b/apps/flutter_parent/lib/screens/calendar/calendar_widget/calendar_filter_screen/calendar_filter_list_interactor.dart @@ -20,12 +20,6 @@ import 'package:flutter_parent/utils/service_locator.dart'; class CalendarFilterListInteractor { Future> getCoursesForSelectedStudent({bool isRefresh = false}) async { var courses = await locator().getObserveeCourses(forceRefresh: isRefresh); - return courses.where(_enrollmentFilter).toList(); - } - - /// Filters enrollments by those associated with the currently selected user - bool _enrollmentFilter(Course course) { - String _studentId = ApiPrefs.getCurrentStudent().id; - return course.enrollments?.any((enrollment) => enrollment.userId == _studentId) ?? false; + return courses.where((course) => course.isValidForCurrentStudent(ApiPrefs.getCurrentStudent().id)).toList(); } } diff --git a/apps/flutter_parent/lib/screens/courses/courses_interactor.dart b/apps/flutter_parent/lib/screens/courses/courses_interactor.dart index 15d9b3416f..dd2b471f01 100644 --- a/apps/flutter_parent/lib/screens/courses/courses_interactor.dart +++ b/apps/flutter_parent/lib/screens/courses/courses_interactor.dart @@ -14,11 +14,14 @@ import 'package:flutter_parent/models/course.dart'; import 'package:flutter_parent/network/api/course_api.dart'; +import 'package:flutter_parent/network/utils/api_prefs.dart'; import 'package:flutter_parent/utils/service_locator.dart'; class CoursesInteractor { - Future> getCourses({bool isRefresh = false}) async { + Future> getCourses({bool isRefresh = false, String studentId = null}) async { var courses = await locator().getObserveeCourses(forceRefresh: isRefresh); - return courses; + var currentStudentId = studentId; + if (currentStudentId == null) currentStudentId = ApiPrefs.getCurrentStudent().id; + return courses.where((course) => course.isValidForCurrentStudent(currentStudentId)).toList(); } } diff --git a/apps/flutter_parent/lib/screens/courses/courses_screen.dart b/apps/flutter_parent/lib/screens/courses/courses_screen.dart index f474e5188c..419666d7b3 100644 --- a/apps/flutter_parent/lib/screens/courses/courses_screen.dart +++ b/apps/flutter_parent/lib/screens/courses/courses_screen.dart @@ -36,7 +36,6 @@ class CoursesScreen extends StatefulWidget { class _CoursesScreenState extends State { User _student; - List _courses = []; Future> _coursesFuture; @@ -56,7 +55,8 @@ class _CoursesScreenState extends State { } } - Future> _loadCourses({bool forceRefresh = false}) => _interactor.getCourses(isRefresh: forceRefresh); + Future> _loadCourses({bool forceRefresh = false}) => + _interactor.getCourses(isRefresh: forceRefresh, studentId: _student?.id?.isEmpty == true ? null : _student.id); @override Widget build(BuildContext context) => _content(context); @@ -64,20 +64,18 @@ class _CoursesScreenState extends State { Widget _content(BuildContext context) { return FutureBuilder( future: _coursesFuture, - builder: (context, snapshot) { + builder: (context, AsyncSnapshot> snapshot) { Widget _body; if (snapshot.hasError) { _body = ErrorPandaWidget(L10n(context).errorLoadingCourses, () => _refreshKey.currentState.show()); } else if (snapshot.hasData) { - _courses = snapshot.data; - final studentCourses = _courses.where(_enrollmentFilter); - _body = (studentCourses == null || studentCourses.isEmpty) + _body = (snapshot.data.isEmpty) ? EmptyPandaWidget( svgPath: 'assets/svg/panda-book.svg', title: L10n(context).noCoursesTitle, subtitle: L10n(context).noCoursesMessage, ) - : _success(studentCourses); + : _success(snapshot.data); } else { return LoadingIndicator(); } @@ -148,11 +146,6 @@ class _CoursesScreenState extends State { ); } - /// Filters enrollments by those associated with the currently selected user - bool _enrollmentFilter(Course course) { - return course.enrollments?.any((enrollment) => enrollment.userId == _student?.id) ?? false; - } - void _courseTapped(context, Course course) { locator().pushRoute(context, PandaRouter.courseDetails(course.id)); } diff --git a/apps/flutter_parent/test/models/course_test.dart b/apps/flutter_parent/test/models/course_test.dart index e755777a87..a17d2383e6 100644 --- a/apps/flutter_parent/test/models/course_test.dart +++ b/apps/flutter_parent/test/models/course_test.dart @@ -15,12 +15,16 @@ import 'package:built_collection/built_collection.dart'; import 'package:flutter_parent/models/course.dart'; import 'package:flutter_parent/models/course_grade.dart'; import 'package:flutter_parent/models/enrollment.dart'; +import 'package:flutter_parent/models/section.dart'; +import 'package:flutter_parent/models/term.dart'; import 'package:test/test.dart'; void main() { final _studentId = '123'; final _gradingPeriodId = '321'; final _course = Course((b) => b..id = 'course_123'); + final futureDate = DateTime.now().add(Duration(days: 10)); + final pastDate = DateTime.now().subtract(Duration(days: 10)); final _enrollment = Enrollment((b) => b ..enrollmentState = 'active' ..userId = _studentId); @@ -78,4 +82,207 @@ void main() { expect(grade, CourseGrade(course, enrollment, forceAllPeriods: false)); }); }); + + group('isValidForParent', () { + test('returns false for accessRestrictedByDate', () { + final course = _course.rebuild((b) => b..accessRestrictedByDate = true); + + final isValid = course.isValidForDate(); + + expect(isValid, isFalse); + }); + + test('returns false for workFlowState completed', () { + final course = _course.rebuild((b) => b + ..workflowState = 'completed' + ..accessRestrictedByDate = false); + + final isValid = course.isValidForDate(); + + expect(isValid, isFalse); + }); + + test('returns false for restrictEnrollmentsToCourseDates and !isWithinCourseDates', () { + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..startAt = futureDate + ..endAt = pastDate); + + final isValid = course.isValidForDate(); + + expect(isValid, isFalse); + }); + + test('returns true for restrictEnrollmentsToCourseDates with isWithinCourseDates', () { + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..startAt = pastDate + ..endAt = futureDate); + + final isValid = course.isValidForDate(); + + expect(isValid, isTrue); + }); + + test('returns false for !restrictEnrollmentsToCourseDates and !isWithinCourseDates & !isWithinTermDates', () { + final term = Term((b) => b + ..id = '' + ..startAt = pastDate + ..endAt = pastDate); + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = false + ..startAt = pastDate + ..endAt = pastDate + ..term = term.toBuilder()); + + final isValid = course.isValidForDate(); + + expect(isValid, isFalse); + }); + + test( + 'returns false for !restrictEnrollmentsToCourseDates and !isWithinCourseDates & !isWithinTermDates & !isWithinAnySection', + () { + final section = Section((b) => b + ..id = '' + ..name = '' + ..startAt = pastDate + ..endAt = pastDate); + final term = Term((b) => b + ..id = '' + ..startAt = pastDate + ..endAt = pastDate); + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = false + ..endAt = pastDate + ..startAt = pastDate + ..term = term.toBuilder() + ..sections = ListBuilder([section])); + + final isValid = course.isValidForDate(); + + expect(isValid, isFalse); + }); + + test('returns true for restrictEnrollmentsToCourseDates and null course dates', () { + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..startAt = null + ..endAt = null); + + final isValid = course.isValidForDate(); + + expect(isValid, isTrue); + }); + + test( + 'returns true for !restrictEnrollmentsToCourseDates with isWithinCourseDates & isWithinTermDates & isWithinAnySection', + () { + final section = Section((b) => b + ..id = '' + ..name = '' + ..startAt = pastDate + ..endAt = futureDate); + final term = Term((b) => b + ..id = '' + ..startAt = pastDate + ..endAt = futureDate); + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = false + ..endAt = futureDate + ..startAt = pastDate + ..term = term.toBuilder() + ..sections = ListBuilder([section])); + + final isValid = course.isValidForDate(); + + expect(isValid, isTrue); + }); + + test( + 'returns true for !restrictEnrollmentsToCourseDates with null dates for isWithinCourseDates & isWithinTermDates & isWithinAnySection', + () { + final section = Section((b) => b + ..id = '' + ..name = '' + ..startAt = null + ..endAt = null); + final term = Term((b) => b + ..id = '' + ..startAt = null + ..endAt = null); + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = false + ..endAt = null + ..startAt = null + ..term = term.toBuilder() + ..sections = ListBuilder([section])); + + final isValid = course.isValidForDate(); + + expect(isValid, isTrue); + }); + + test('returns true for !restrictEnrollmentsToCourseDates with null course, section, and term dates', () { + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = false + ..endAt = null + ..startAt = null + ..term = null + ..sections = null); + + final isValid = course.isValidForDate(); + + expect(isValid, isTrue); + }); + }); + + group('isValidForCurrentStudent', () { + test('returns true for valid date with valid enrollment', () { + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..endAt = futureDate + ..enrollments = ListBuilder([_enrollment])); + + final isValid = course.isValidForCurrentStudent(_studentId); + + expect(isValid, isTrue); + }); + + test('returns false for valid date with invalid enrollment', () { + final invalidEnrollment = Enrollment((b) => b + ..enrollmentState = 'active' + ..userId = '789'); + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..endAt = futureDate + ..enrollments = ListBuilder([invalidEnrollment])); + + final isValid = course.isValidForCurrentStudent(_studentId); + + expect(isValid, isFalse); + }); + + test('returns false for invalid date with valid enrollment', () { + final course = _course.rebuild((b) => b + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..endAt = pastDate + ..enrollments = ListBuilder([_enrollment])); + + final isValid = course.isValidForCurrentStudent(_studentId); + + expect(isValid, isFalse); + }); + }); } diff --git a/apps/flutter_parent/test/screens/calendar/calendar_widget/calendar_filter_list_interactor_test.dart b/apps/flutter_parent/test/screens/calendar/calendar_widget/calendar_filter_list_interactor_test.dart index 01666c10c8..f069401e29 100644 --- a/apps/flutter_parent/test/screens/calendar/calendar_widget/calendar_filter_list_interactor_test.dart +++ b/apps/flutter_parent/test/screens/calendar/calendar_widget/calendar_filter_list_interactor_test.dart @@ -12,10 +12,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import 'dart:convert'; + import 'package:built_collection/built_collection.dart'; import 'package:flutter_parent/models/course.dart'; import 'package:flutter_parent/models/enrollment.dart'; -import 'package:flutter_parent/models/user.dart'; +import 'package:flutter_parent/models/serializers.dart'; import 'package:flutter_parent/network/api/course_api.dart'; import 'package:flutter_parent/network/utils/api_prefs.dart'; import 'package:flutter_parent/screens/calendar/calendar_widget/calendar_filter_screen/calendar_filter_list_interactor.dart'; @@ -23,46 +25,76 @@ import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import '../../../utils/canvas_model_utils.dart'; +import '../../../utils/platform_config.dart'; import '../../../utils/test_app.dart'; +import '../../../utils/test_helpers/mock_helpers.dart'; void main() { + final _api = MockCourseApi(); + final _user = CanvasModelTestUtils.mockUser(id: '123'); + + setupTestLocator((locator) { + locator.registerLazySingleton(() => _api); + }); + + setUp(() async { + reset(_api); + await setupPlatformChannels( + config: PlatformConfig(mockApiPrefs: {ApiPrefs.KEY_CURRENT_STUDENT: json.encode(serialize(_user))})); + }); + test('getCourses calls API', () async { - final api = _MockCourseApi(); - setupTestLocator((locator) => locator.registerLazySingleton(() => api)); - when(api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => []); + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => []); await CalendarFilterListInteractor().getCoursesForSelectedStudent(isRefresh: true); - verify(api.getObserveeCourses(forceRefresh: true)); + verify(_api.getObserveeCourses(forceRefresh: true)); }); test('getCourses filters by student enrollment', () async { - final studentId = 'student123'; - await setupPlatformChannels(); - User user = CanvasModelTestUtils.mockUser(id: studentId); - await ApiPrefs.setCurrentStudent(user); - final apiCourses = [ Course(), Course((c) => c ..enrollments = ListBuilder([ Enrollment((e) => e - ..userId = studentId + ..userId = _user.id ..enrollmentState = '') ])), Course(), ]; final expectedCourses = [apiCourses[1]]; - final api = _MockCourseApi(); - setupTestLocator((locator) => locator.registerLazySingleton(() => api)); + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => apiCourses); + + final actualCourses = await CalendarFilterListInteractor().getCoursesForSelectedStudent(isRefresh: true); + + expect(actualCourses, expectedCourses); + }); + + test('getCourses filters by valid course dates', () async { + final apiCourses = [ + Course((c) => c + ..accessRestrictedByDate = true + ..enrollments = ListBuilder([ + Enrollment((e) => e + ..userId = _user.id + ..enrollmentState = '') + ])), + Course((c) => c + ..accessRestrictedByDate = false + ..endAt = DateTime.now().add(Duration(days: 10)) + ..enrollments = ListBuilder([ + Enrollment((e) => e + ..userId = _user.id + ..enrollmentState = '') + ])) + ]; + final expectedCourses = [apiCourses[1]]; - when(api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => apiCourses); + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => apiCourses); final actualCourses = await CalendarFilterListInteractor().getCoursesForSelectedStudent(isRefresh: true); expect(actualCourses, expectedCourses); }); } - -class _MockCourseApi extends Mock implements CourseApi {} diff --git a/apps/flutter_parent/test/screens/courses/courses_interactor_test.dart b/apps/flutter_parent/test/screens/courses/courses_interactor_test.dart index 25f5378188..0218e89218 100644 --- a/apps/flutter_parent/test/screens/courses/courses_interactor_test.dart +++ b/apps/flutter_parent/test/screens/courses/courses_interactor_test.dart @@ -12,23 +12,114 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +import 'dart:convert'; + +import 'package:built_collection/built_collection.dart'; +import 'package:flutter_parent/models/course.dart'; +import 'package:flutter_parent/models/enrollment.dart'; +import 'package:flutter_parent/models/serializers.dart'; +import 'package:flutter_parent/models/user.dart'; import 'package:flutter_parent/network/api/course_api.dart'; +import 'package:flutter_parent/network/utils/api_prefs.dart'; import 'package:flutter_parent/screens/courses/courses_interactor.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; +import '../../utils/platform_config.dart'; import '../../utils/test_app.dart'; +import '../../utils/test_helpers/mock_helpers.dart'; void main() { + final _api = MockCourseApi(); + + final _studentId = '123'; + final _invalidStudentId = '789'; + + final _futureDate = DateTime.now().add(Duration(days: 10)); + final _pastDate = DateTime.now().subtract(Duration(days: 10)); + + var _student = User((b) => b + ..id = _studentId + ..name = 'UserName' + ..sortableName = 'Sortable Name' + ..build()); + + final _enrollment = Enrollment((b) => b + ..enrollmentState = 'active' + ..userId = _studentId); + + final _invalidEnrollment = Enrollment((b) => b + ..enrollmentState = 'active' + ..userId = _invalidStudentId); + + final _course = Course((b) => b + ..id = 'course_123' + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..endAt = _futureDate + ..enrollments = ListBuilder([_enrollment])); + + final _invalidCourse = Course((b) => b + ..id = 'course_456' + ..accessRestrictedByDate = false + ..restrictEnrollmentsToCourseDates = true + ..endAt = _futureDate + ..enrollments = ListBuilder([_invalidEnrollment])); + + setupTestLocator((locator) { + locator.registerLazySingleton(() => _api); + }); + + setUp(() async { + reset(_api); + await setupPlatformChannels( + config: PlatformConfig(mockApiPrefs: {ApiPrefs.KEY_CURRENT_STUDENT: json.encode(serialize(_student))})); + }); + test('getCourses calls API', () async { - final api = _MockCourseApi(); - setupTestLocator((locator) => locator.registerLazySingleton(() => api)); - when(api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => []); + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => []); - await CoursesInteractor().getCourses(isRefresh: true); + await CoursesInteractor().getCourses(isRefresh: true, studentId: null); - verify(api.getObserveeCourses(forceRefresh: true)); + verify(_api.getObserveeCourses(forceRefresh: true)); }); -} -class _MockCourseApi extends Mock implements CourseApi {} + test('getCourses returns only courses for ApiPrefs.currentStudent.id and is valid', () async { + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => [_course, _invalidCourse]); + + final result = await CoursesInteractor().getCourses(isRefresh: true, studentId: null); + + verify(_api.getObserveeCourses(forceRefresh: true)); + expect(result.length, 1); + expect(result.first.id, _course.id); + }); + + test('getCourses returns only courses for studentId parameter and is valid', () async { + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => [_course, _invalidCourse]); + + final result = await CoursesInteractor().getCourses(isRefresh: true, studentId: _invalidStudentId); + + verify(_api.getObserveeCourses(forceRefresh: true)); + expect(result.length, 1); + expect(result.first.id, _invalidCourse.id); + }); + + test('getCourses returns no courses for valid studentId but invalid dates', () async { + final badDatesCourse = _course.rebuild((b) => b..endAt = _pastDate); + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => [badDatesCourse]); + + final result = await CoursesInteractor().getCourses(isRefresh: true, studentId: _studentId); + + verify(_api.getObserveeCourses(forceRefresh: true)); + expect(result, isEmpty); + }); + + test('getCourses returns no courses for invalid studentId but valid dates', () async { + when(_api.getObserveeCourses(forceRefresh: true)).thenAnswer((_) async => [_invalidCourse]); + + final result = await CoursesInteractor().getCourses(isRefresh: true, studentId: _studentId); + + verify(_api.getObserveeCourses(forceRefresh: true)); + expect(result, isEmpty); + }); +} diff --git a/apps/flutter_parent/test/screens/courses/courses_screen_test.dart b/apps/flutter_parent/test/screens/courses/courses_screen_test.dart index 88b5ace164..62c3d22d4c 100644 --- a/apps/flutter_parent/test/screens/courses/courses_screen_test.dart +++ b/apps/flutter_parent/test/screens/courses/courses_screen_test.dart @@ -41,6 +41,7 @@ import 'package:provider/provider.dart'; import '../../utils/accessibility_utils.dart'; import '../../utils/platform_config.dart'; import '../../utils/test_app.dart'; +import '../../utils/test_helpers/mock_helpers.dart'; void main() { AppLocalizations l10n = AppLocalizations(); @@ -49,8 +50,8 @@ void main() { setupTestLocator((locator) { locator.registerFactory(() => mockInteractor); locator.registerFactory(() => _MockCourseDetailsInteractor()); - locator.registerFactory(() => _MockAssignmentApi()); - locator.registerFactory(() => _MockCourseApi()); + locator.registerFactory(() => MockAssignmentApi()); + locator.registerFactory(() => MockCourseApi()); locator.registerFactory(() => QuickNav()); locator.registerLazySingleton(() => Analytics()); @@ -192,65 +193,6 @@ void main() { final gradeWidget = find.text('90%'); expect(gradeWidget, findsNWidgets(courses.length)); }); - - testWidgetsWithAccessibilityChecks('only shows courses for selected user', (tester) async { - var numCourses1 = 3; - var numCourses2 = 1; - var student1 = _mockStudent('1'); - var student2 = _mockStudent('2'); - var courses1 = generateCoursesForStudent(student1.id, numberOfCourses: numCourses1); - var courses2 = generateCoursesForStudent(student2.id, numberOfCourses: numCourses2); - - SelectedStudentNotifier notifier = SelectedStudentNotifier(); - _setupLocator(_MockedCoursesInteractor(courses: courses1..addAll(courses2)), notifier: notifier); - - await tester.pumpWidget(_testableMaterialWidget(notifier: notifier)); - await tester.pumpAndSettle(); - - // First student will be selected, verify that we show their courses - final listTileWidget = find.byType(ListTile); - expect(listTileWidget, findsNWidgets(numCourses1)); - - // Select second student - notifier.update(student2); - await tester.pump(); - await tester.pumpAndSettle(); - - // Check for the courses of the second student - expect(listTileWidget, findsNWidgets(numCourses2)); - }); - - testWidgetsWithAccessibilityChecks('updates courses when selected user changes', (tester) async { - var numCourses1 = 3; - var numCourses2 = 1; - var student1 = _mockStudent('1'); - var student2 = _mockStudent('2'); - var courses1 = generateCoursesForStudent(student1.id, numberOfCourses: numCourses1); - var courses2 = generateCoursesForStudent(student2.id, numberOfCourses: numCourses2); - - SelectedStudentNotifier notifier = SelectedStudentNotifier(); - var interactor = _MockCoursesInteractor(); - when(interactor.getCourses(isRefresh: anyNamed('isRefresh'))) - .thenAnswer((_) => Future.value([...courses1, ...courses2])); - - _setupLocator(interactor, notifier: notifier); - - await tester.pumpWidget(_testableMaterialWidget(notifier: notifier)); - await tester.pumpAndSettle(); - - // First student will be selected, verify that we show their courses - expect(find.byType(ListTile), findsNWidgets(numCourses1)); - - // Select second student - notifier.update(student2); - await tester.pumpAndSettle(); - - // Check for the courses of the second student - expect(find.byType(ListTile), findsNWidgets(numCourses2)); - - // Verify that we called getCourses twice, once on initial load and again when we switched students - verify(interactor.getCourses(isRefresh: true)).called(2); - }); }); group('Interaction', () { @@ -313,20 +255,14 @@ class _MockedCoursesInteractor extends CoursesInteractor { _MockedCoursesInteractor({this.courses}); @override - Future> getCourses({bool isRefresh = false}) async { + Future> getCourses({bool isRefresh = false, String studentId = null}) async { if (error) throw ''; return courses ?? [_mockCourse('1')]; } } -class _MockCoursesInteractor extends Mock implements CoursesInteractor {} - class _MockCourseDetailsInteractor extends CourseDetailsInteractor {} -class _MockAssignmentApi extends Mock implements AssignmentApi {} - -class _MockCourseApi extends Mock implements CourseApi {} - class _MockAlertCountNotifier extends Mock implements AlertCountNotifier {} List generateCoursesForStudent(String userId, {int numberOfCourses = 1}) { diff --git a/apps/flutter_parent/test/screens/dashboard/dashboard_screen_test.dart b/apps/flutter_parent/test/screens/dashboard/dashboard_screen_test.dart index 37a8fd1112..043311fa3a 100644 --- a/apps/flutter_parent/test/screens/dashboard/dashboard_screen_test.dart +++ b/apps/flutter_parent/test/screens/dashboard/dashboard_screen_test.dart @@ -1243,7 +1243,7 @@ class MockInteractor extends DashboardInteractor { class MockCoursesInteractor extends CoursesInteractor { @override - Future> getCourses({bool isRefresh = false}) async { + Future> getCourses({bool isRefresh = false, String studentId = null}) async { var courses = List(); return courses; } diff --git a/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart b/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart index 3291b1957d..963fe5656b 100644 --- a/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart +++ b/apps/flutter_parent/test/utils/test_helpers/mock_helpers.dart @@ -23,6 +23,7 @@ import 'package:dio/dio.dart'; import 'package:firebase_remote_config/firebase_remote_config.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_parent/network/api/alert_api.dart'; +import 'package:flutter_parent/network/api/assignment_api.dart'; import 'package:flutter_parent/network/api/auth_api.dart'; import 'package:flutter_parent/network/api/course_api.dart'; import 'package:flutter_parent/network/api/enrollments_api.dart'; @@ -66,6 +67,8 @@ class MockAlertsApi extends Mock implements AlertsApi {} class MockAlertCountNotifier extends Mock implements AlertCountNotifier {} +class MockAssignmentApi extends Mock implements AssignmentApi {} + class MockAuthApi extends Mock implements AuthApi {} class MockBarcodeScanner extends Mock implements BarcodeScanVeneer {}