Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f58466d25 | ||
|
|
c6f280ff72 | ||
|
|
f8b4e8cfd3 | ||
|
|
1fa2f53d35 | ||
|
|
5e22c3a391 | ||
|
|
8df7f7c74c | ||
|
|
45139640eb | ||
|
|
aa568cb628 | ||
|
|
4160a79574 | ||
|
|
8d04c10f48 | ||
|
|
02bfe60e8b | ||
|
|
4ff7b1a5ea | ||
|
|
6cd211f744 |
@@ -74,9 +74,9 @@ jobs:
|
||||
SUFFIX=$((SUFFIX + 1))
|
||||
TAG="v${BASE}.${SUFFIX}"
|
||||
done
|
||||
echo "version_name=$BASE" >> "$GITHUB_OUTPUT"
|
||||
echo "version_name=${TAG#v}" >> "$GITHUB_OUTPUT"
|
||||
echo "version_tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "Generated version: $BASE (tag: $TAG)"
|
||||
echo "Generated version: ${TAG#v} (tag: $TAG)"
|
||||
|
||||
- name: Restore keystore from secret
|
||||
# Ключ подписи передаётся как base64-секрет и декодируется в файл.
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
ORG_GRADLE_JVMARGS: '-Xmx2048m'
|
||||
GRADLE_OPTS: '-Dorg.gradle.daemon=false'
|
||||
GRADLE_OPTS: '-Dorg.gradle.daemon=true'
|
||||
|
||||
- name: Determine APK path
|
||||
id: apk
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
cd /tmp
|
||||
wget -q https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.12%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz
|
||||
tar -xzf OpenJDK17U-jdk_x64_linux_hotspot_17.0.12_7.tar.gz -C /opt
|
||||
mv /opt/jdk-17.0.12+7 /opt/java17
|
||||
export JAVA_HOME=/opt/java17
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
echo "JAVA_HOME=/opt/java17" >> $GITHUB_ENV
|
||||
echo "$JAVA_HOME/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Install Android SDK
|
||||
run: |
|
||||
set -euxo pipefail
|
||||
echo "ANDROID_HOME=/opt/android-sdk" >> "$GITHUB_ENV"
|
||||
mkdir -p /opt/android-sdk/cmdline-tools
|
||||
wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/clt.zip
|
||||
unzip -q /tmp/clt.zip -d /tmp/clt
|
||||
mv /tmp/clt/cmdline-tools /opt/android-sdk/cmdline-tools/latest
|
||||
yes | /opt/android-sdk/cmdline-tools/latest/bin/sdkmanager --licenses >/dev/null 2>&1 || true
|
||||
/opt/android-sdk/cmdline-tools/latest/bin/sdkmanager \
|
||||
"platform-tools" \
|
||||
"platforms;android-36.1" \
|
||||
"build-tools;36.1.0"
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Run JVM unit tests
|
||||
run: ./gradlew :app:testDebugUnitTest --console=plain
|
||||
env:
|
||||
ANDROID_HOME: /opt/android-sdk
|
||||
GRADLE_OPTS: '-Dorg.gradle.daemon=false'
|
||||
@@ -109,11 +109,7 @@ dependencies {
|
||||
implementation(libs.pdfbox.android)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
testImplementation("org.apache.pdfbox:pdfbox:3.0.4")
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
|
||||
androidTestImplementation(libs.androidx.test.runner)
|
||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||
debugImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
Факультет автоматики и вычислительной техники (ОРУ)
|
||||
Группа: ИВТб-3304-05-00
|
||||
|
||||
Понедельник (14.09.26): <пусто, пар нет>
|
||||
Вторник (15.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - ИВТб-3304-05-00 Объектно-ориентированный анализ и проектирование Лекция Преподаватель -.П. 2-202; ИВТб-3304-05-00 Разработка Web-приложений Лекция Преподаватель -.П. 1-236
|
||||
11:45-13:15 - ИВТб-3304-05-00 Инструментальные средства проектирования компонентов вычислительной техники Лекция Семеновых В.И. 2-201; ИВТб-3304-05-00 Предметно-ориентированные автоматизированные информационные системы Лекция Прозорова Ю.А. 1-313; ИВТб-3304-05-00 Сценарное и функциональное программирование Лекция Преподаватель -.П. 1-311
|
||||
14:00-15:30 - ИВТб-3304-05-00 Инструментальные средства проектирования компонентов вычислительной техники Лабораторная работа Семеновых В.И. 2-201; ИВТб-3304-05-00 Предметно-ориентированные автоматизированные информационные системы Лабораторная работа Прозорова Ю.А. 1-236; ИВТб-3304-05-00 Сценарное и функциональное программирование Лабораторная работа Преподаватель -.П. 1-114
|
||||
15:45-17:15 - ИВТб-3304-05-00 Инструментальные средства проектирования компонентов вычислительной техники Лабораторная работа Семеновых В.И. 2-201; ИВТб-3304-05-00 Предметно-ориентированные автоматизированные информационные системы Лабораторная работа Прозорова Ю.А. 1-236; ИВТб-3304-05-00 Сценарное и функциональное программирование Лабораторная работа Преподаватель -.П. 1-114
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Среда (16.09.26): 08:20-09:50 - Введение в архитектуру ЭВМ Лекция Страбыкин Д.А. 1-242
|
||||
10:00-11:30 - Теория компиляторов и формальные грамматики Лекция Мельцов В.Ю. 1-242
|
||||
11:45-13:15 - <пусто>
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Четверг (17.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - ИВТб-3304-05-00, 01 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 131) Практическое занятие Трушкова Е.Л. 9-115; ИВТб-3304-05-00, 01 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 039) Практическое занятие Салопин О.М. 9-301 ИВТб-3304-05-00, 02 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 039) Практическое занятие Салопин О.М. 9-301; ИВТб-3304-05-00, 02 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (21 285) Практическое занятие Хвостенко С.Ю. 9-209.; ИВТб-3304-05-00, 01 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 124) Практическое занятие Пластинина В.Б. 9-208
|
||||
14:00-15:30 - Архитектура операционных систем и сервисное программное обеспечение Лекция Преподаватель -.П. 1-439
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Пятница (18.09.26): 08:20-09:50 - ИВТб-3304-05-00, 01 подгруппа Введение в архитектуру ЭВМ Лабораторная работа Страбыкин Д.А. 1-117; ИВТб-3304-05-00, 02 подгруппа Введение в архитектуру ЭВМ Лабораторная работа Страбыкин Д.А. 1-117_
|
||||
10:00-11:30 - ИВТб-3304-05-00, 01 подгруппа Введение в архитектуру ЭВМ Лабораторная работа Страбыкин Д.А. 1-117; ИВТб-3304-05-00, 02 подгруппа Введение в архитектуру ЭВМ Лабораторная работа Страбыкин Д.А. 1-117_
|
||||
11:45-13:15 - Введение в архитектуру ЭВМ Практическое занятие Страбыкин Д.А. 2-207
|
||||
14:00-15:30 - Философия Практическое занятие Коротышева Н.Н. 2-407
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Суббота (19.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - <пусто>
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Воскресенье (20.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - <пусто>
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Понедельник (21.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - ИВТб-3304-05-00, 01 подгруппа Теория компиляторов и формальные грамматики Лабораторная работа Караваева О.В. 2-308; ИВТб-3304-05-00, 02 подгруппа Теория компиляторов и формальные грамматики Лабораторная работа Караваева О.В. 2-308
|
||||
11:45-13:15 - ИВТб-3304-05-00, 01 подгруппа Теория компиляторов и формальные грамматики Лабораторная работа Караваева О.В. 2-308; ИВТб-3304-05-00, 02 подгруппа Теория компиляторов и формальные грамматики Лабораторная работа Караваева О.В. 2-308_
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Вторник (22.09.26): 08:20-09:50 - Объектно-ориентированный анализ и проектирование Практическое занятие Шмакова Н.А. 2-202
|
||||
10:00-11:30 - ИВТб-3304-05-00 Инструментальные средства проектирования компонентов вычислительной техники Лекция Семеновых В.И. 2-201; ИВТб-3304-05-00 Предметно-ориентированные автоматизированные информационные системы Лекция Прозорова Ю.А. 1-313; ИВТб-3304-05-00 Сценарное и функциональное программирование Лекция Преподаватель -.П. 1-311
|
||||
11:45-13:15 - ИВТб-3304-05-00 Объектно-ориентированный анализ и проектирование Лекция Преподаватель -.П. 2-202; ИВТб-3304-05-00 Проектирование систем на ПЛИС Лекция Крутиков А.К. 1-313; ИВТб-3304-05-00 Разработка Web-приложений Лекция Преподаватель -.П. 1-236
|
||||
14:00-15:30 - ИВТб-3304-05-00 Проектирование систем на ПЛИС Практическое занятие Крутиков А.К. 1-109; ИВТб-3304-05-00 Разработка Web-приложений Практическое занятие Пащенко Д.Э. 1-116
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Среда (23.09.26): 08:20-09:50 - Теория компиляторов и формальные грамматики Практическое занятие Караваева О.В. 2-201
|
||||
10:00-11:30 - Введение в архитектуру ЭВМ Лекция Страбыкин Д.А. 1-242
|
||||
11:45-13:15 - Архитектура операционных систем и сервисное программное обеспечение Лекция Преподаватель -.П. 1-242
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Четверг (24.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - ИВТб-3304-05-00, 01 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 131) Практическое занятие Трушкова Е.Л. 9-115; ИВТб-3304-05-00, 01 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 039) Практическое занятие Салопин О.М. 9-301 ИВТб-3304-05-00, 02 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 039) Практическое занятие Салопин О.М. 9-301; ИВТб-3304-05-00, 02 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (21 285) Практическое занятие Хвостенко С.Ю. 9-101; ИВТб-3304-05-00, 01 подгруппа Элективные дисциплины (модули) по физической культуре и спорту Практическое занятие (11 124) Практическое занятие Пластинина В.Б. 9-208
|
||||
14:00-15:30 - ИВТб-3304-05-00 Объектно-ориентированный анализ и проектирование Лабораторная работа Шмакова Н.А. 2-202; ИВТб-3304-05-00 Проектирование систем на ПЛИС Лабораторная работа Крутиков А.К. 1-239; ИВТб-3304-05-00 Разработка Web-приложений Лабораторная работа Пащенко Д.Э. 1-109
|
||||
15:45-17:15 - ИВТб-3304-05-00 Объектно-ориентированный анализ и проектирование Лабораторная работа Шмакова Н.А. 2-202; ИВТб-3304-05-00 Проектирование систем на ПЛИС Лабораторная работа Крутиков А.К. 1-239; ИВТб-3304-05-00 Разработка Web-приложений Лабораторная работа Пащенко Д.Э. 1-109
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Пятница (25.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - <пусто>
|
||||
14:00-15:30 - Теория компиляторов и формальные грамматики Лекция Мельцов В.Ю. 1-242
|
||||
15:45-17:15 - Архитектура операционных систем и сервисное программное обеспечение Лабораторная работа Караваева О.В. 2-201
|
||||
17:20-18:50 - Архитектура операционных систем и сервисное программное обеспечение Лабораторная работа Караваева О.В. 2-201
|
||||
18:55-20:25 - <пусто>
|
||||
Суббота (26.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - <пусто>
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Воскресенье (27.09.26): 08:20-09:50 - <пусто>
|
||||
10:00-11:30 - <пусто>
|
||||
11:45-13:15 - <пусто>
|
||||
14:00-15:30 - <пусто>
|
||||
15:45-17:15 - <пусто>
|
||||
17:20-18:50 - <пусто>
|
||||
18:55-20:25 - <пусто>
|
||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
package agzam.vyatsu
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("agzam.vyatsu", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package agzam.vyatsu.data.remote.pdf
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PdfDumpTest {
|
||||
|
||||
@Test
|
||||
fun dumpParsedSchedule() {
|
||||
val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
val bytes = instrumentation.context.assets.open("schedule.pdf").use { it.readBytes() }
|
||||
|
||||
val days = PdfScheduleParser.parse(bytes)
|
||||
|
||||
val target = instrumentation.targetContext.getExternalFilesDir(null)!!.resolve("parsed.txt")
|
||||
target.writeText(days.joinToString(separator = "\n"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package agzam.vyatsu.data.remote.pdf
|
||||
|
||||
import agzam.vyatsu.domain.model.Day
|
||||
import agzam.vyatsu.domain.model.Lesson
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PdfScheduleParserTest {
|
||||
|
||||
companion object {
|
||||
private val days: List<Day> by lazy {
|
||||
val bytes = InstrumentationRegistry.getInstrumentation().context.assets
|
||||
.open("schedule.pdf")
|
||||
.use { it.readBytes() }
|
||||
PdfScheduleParser.parse(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
private fun day(date: String): Day = days.first { it.date == LocalDate.parse(date) }
|
||||
|
||||
private fun lessons(date: String, start: String): List<Lesson> =
|
||||
day(date).intervals
|
||||
.first { it.start == LocalTime.parse(start) }
|
||||
.lessons
|
||||
.filter { it.raw.isNotBlank() }
|
||||
|
||||
private fun allLessons(date: String): List<Lesson> =
|
||||
day(date).intervals.flatMap { it.lessons }.filter { it.raw.isNotBlank() }
|
||||
|
||||
private fun Lesson.canonical(): String =
|
||||
listOf(subject, type.name, room, teacher, subgroup).joinToString("|")
|
||||
|
||||
@Test
|
||||
fun parsesWholeTwoWeekPeriod() {
|
||||
val expectedDates = (0..13L).map { LocalDate.of(2026, 9, 14).plusDays(it) }
|
||||
|
||||
assertEquals(expectedDates, days.map { it.date })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun everyDayHasSevenStandardIntervals() {
|
||||
val expectedTimes = listOf(
|
||||
LocalTime.of(8, 20) to LocalTime.of(9, 50),
|
||||
LocalTime.of(10, 0) to LocalTime.of(11, 30),
|
||||
LocalTime.of(11, 45) to LocalTime.of(13, 15),
|
||||
LocalTime.of(14, 0) to LocalTime.of(15, 30),
|
||||
LocalTime.of(15, 45) to LocalTime.of(17, 15),
|
||||
LocalTime.of(17, 20) to LocalTime.of(18, 50),
|
||||
LocalTime.of(18, 55) to LocalTime.of(20, 25)
|
||||
)
|
||||
|
||||
for (day in days) {
|
||||
assertEquals(day.date.toString(), expectedTimes, day.intervals.map { it.start to it.end })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyDaysHaveNoLessons() {
|
||||
val emptyDays = listOf("2026-09-14", "2026-09-19", "2026-09-20", "2026-09-26", "2026-09-27")
|
||||
|
||||
for (date in emptyDays) {
|
||||
assertEquals(date, emptyList<Lesson>(), allLessons(date))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesTwoLecturesInOneSlot() {
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Объектно-ориентированный анализ и проектирование|LECTURE|2-202|Преподаватель -.П.|null",
|
||||
"Разработка Web-приложений|LECTURE|1-236|Преподаватель -.П.|null"
|
||||
),
|
||||
lessons("2026-09-15", "10:00").map { it.canonical() }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesThreeLecturesInOneSlot() {
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Инструментальные средства проектирования компонентов вычислительной техники|LECTURE|2-201|Семеновых В.И.|null",
|
||||
"Предметно-ориентированные автоматизированные информационные системы|LECTURE|1-313|Прозорова Ю.А.|null",
|
||||
"Сценарное и функциональное программирование|LECTURE|1-311|Преподаватель -.П.|null"
|
||||
),
|
||||
lessons("2026-09-15", "11:45").map { it.canonical() }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesLaboratoryWork() {
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Инструментальные средства проектирования компонентов вычислительной техники|LABORATORY|2-201|Семеновых В.И.|null",
|
||||
"Предметно-ориентированные автоматизированные информационные системы|LABORATORY|1-236|Прозорова Ю.А.|null",
|
||||
"Сценарное и функциональное программирование|LABORATORY|1-114|Преподаватель -.П.|null"
|
||||
),
|
||||
lessons("2026-09-15", "14:00").map { it.canonical() }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesWednesdayLectures() {
|
||||
assertEquals(
|
||||
listOf("Введение в архитектуру ЭВМ|LECTURE|1-242|Страбыкин Д.А.|null"),
|
||||
lessons("2026-09-16", "08:20").map { it.canonical() }
|
||||
)
|
||||
assertEquals(
|
||||
listOf("Теория компиляторов и формальные грамматики|LECTURE|1-242|Мельцов В.Ю.|null"),
|
||||
lessons("2026-09-16", "10:00").map { it.canonical() }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesPhysicalEducationSubgroups() {
|
||||
val parsed = lessons("2026-09-17", "11:45").filter { it.room != null }
|
||||
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Элективные дисциплины (модули) по физической культуре и спорту|PRACTICE|9-115|Трушкова Е.Л.|01 подгруппа",
|
||||
"Элективные дисциплины (модули) по физической культуре и спорту|PRACTICE|9-301|Салопин О.М.|01 подгруппа",
|
||||
"Элективные дисциплины (модули) по физической культуре и спорту|PRACTICE|9-301|Салопин О.М.|02 подгруппа"
|
||||
),
|
||||
parsed.map { it.canonical() }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keepsRawTextWhenRoomHasTrailingPunctuation() {
|
||||
val unparsed = lessons("2026-09-17", "11:45").filter { it.room == null }
|
||||
|
||||
assertEquals(2, unparsed.size)
|
||||
assertTrue(unparsed.any { it.raw.contains("9-209.") })
|
||||
assertTrue(unparsed.any { it.raw.contains("9-208__") })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesFridaySubgroupLaboratories() {
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Введение в архитектуру ЭВМ|LABORATORY|1-117|Страбыкин Д.А.|01 подгруппа",
|
||||
"Введение в архитектуру ЭВМ|LABORATORY|1-117_|Страбыкин Д.А.|02 подгруппа"
|
||||
),
|
||||
lessons("2026-09-18", "08:20").map { it.canonical() }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesSecondWeekMondayLaboratories() {
|
||||
assertEquals(
|
||||
listOf(
|
||||
"Теория компиляторов и формальные грамматики|LABORATORY|2-308|Караваева О.В.|01 подгруппа",
|
||||
"Теория компиляторов и формальные грамматики|LABORATORY|2-308_|Караваева О.В.|02 подгруппа"
|
||||
),
|
||||
lessons("2026-09-21", "10:00").map { it.canonical() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,9 +108,13 @@ object PdfScheduleParser {
|
||||
|
||||
for (dayCell in daysColumn.cells) {
|
||||
val day = content(dayCell).split(' ')
|
||||
if(day.size == 1) {
|
||||
Log.v(TAG, "day.first(): ${day.first()}")
|
||||
}
|
||||
if(day.size == 1 && day.first() == "День") {
|
||||
val groupCell = select(lessonsColumn, dayCell).firstOrNull() ?: continue
|
||||
group = content(groupCell)
|
||||
Log.v(TAG, "Group: $group")
|
||||
continue
|
||||
}
|
||||
if(day.size != 2) {
|
||||
@@ -164,7 +168,7 @@ object PdfScheduleParser {
|
||||
val teacher = raw.substring(typeMatch.last().range.last+1, raw.length - room.length).trim()
|
||||
var subgroup: String? = null
|
||||
Log.d(TAG, "$subject $group")
|
||||
if(group != null && subject.startsWith("$group,")) {
|
||||
if(group != null && (subject.startsWith("$group,") || subject.startsWith("$group "))) {
|
||||
subject = subject.substring(group.length + 1).trim()
|
||||
val subgroupMatch = SUBGROUP_REGEX.find(subject)
|
||||
if(subgroupMatch != null) {
|
||||
@@ -197,6 +201,7 @@ object PdfScheduleParser {
|
||||
|
||||
/** Вывод таблички из PDF в SVG формате + отладочные жлементы из [Pdf2Table.asTable] **/
|
||||
Log.v(SVG_TAG, "====")
|
||||
Log.v(SVG_TAG+ "-" + (pageIndex+1), "====")
|
||||
for(line in svg.asSvg().split("\n")) {
|
||||
if(line.length > 250) {
|
||||
Log.v(SVG_TAG, "length: " + line.length)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package agzam.vyatsu
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package agzam.vyatsu.data.local
|
||||
|
||||
import agzam.vyatsu.domain.model.LessonType
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
|
||||
class ConvertersTest {
|
||||
|
||||
private val converters = Converters()
|
||||
|
||||
@Test
|
||||
fun localDate_roundTripsThroughEpochDay() {
|
||||
val date = LocalDate.of(2026, 9, 14)
|
||||
assertEquals(date, converters.epochDayToLocalDate(converters.localDateToEpochDay(date)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullDate_staysNull() {
|
||||
assertNull(converters.localDateToEpochDay(null))
|
||||
assertNull(converters.epochDayToLocalDate(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localTime_roundTripsThroughMinutes() {
|
||||
assertEquals(
|
||||
LocalTime.of(8, 20),
|
||||
converters.minutesToLocalTime(converters.localTimeToMinutes(LocalTime.of(8, 20)))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localTime_boundariesMapToMinutes() {
|
||||
assertEquals(0, converters.localTimeToMinutes(LocalTime.of(0, 0)))
|
||||
assertEquals(1439, converters.localTimeToMinutes(LocalTime.of(23, 59)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullTime_staysNull() {
|
||||
assertNull(converters.localTimeToMinutes(null))
|
||||
assertNull(converters.minutesToLocalTime(null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lessonType_roundTripsEveryValue() {
|
||||
for (type in LessonType.entries) {
|
||||
assertEquals(type, converters.stringToLessonType(converters.lessonTypeToString(type)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unknownLessonType_isOther() {
|
||||
assertEquals(LessonType.OTHER, converters.stringToLessonType("NOPE"))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package agzam.vyatsu.data.remote
|
||||
|
||||
import agzam.vyatsu.domain.model.DateRange
|
||||
import agzam.vyatsu.domain.model.Season
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import java.io.InputStream
|
||||
|
||||
class GroupsParserTest {
|
||||
|
||||
private val faculty = "Факультет автоматики и вычислительной техники (ОРУ)"
|
||||
private val facultyShort = "ФАВТ (Факультет автоматики и вычислительной техники)"
|
||||
|
||||
private fun page(vararg blocks: String): String = blocks.joinToString("\n")
|
||||
|
||||
private fun facultyDiv(name: String): String = """<div class="fak_name">$name</div>"""
|
||||
|
||||
private fun groupDiv(periodId: String, name: String): String =
|
||||
"""<div class="grpPeriod" data-grp_period_id="$periodId">$name</div>"""
|
||||
|
||||
private fun pdfLink(url: String): String = """<a href="$url">x</a>"""
|
||||
|
||||
private fun parse(html: String): ParsedGroups =
|
||||
GroupsParser.parseStream(html.byteInputStream(Charsets.UTF_8))
|
||||
|
||||
private fun parseLineByLine(html: String): ParsedGroups {
|
||||
val lines = html.split("\n").map { "$it\n" }
|
||||
var index = 0
|
||||
val stream = object : InputStream() {
|
||||
override fun read(): Int = -1
|
||||
override fun read(b: ByteArray, off: Int, len: Int): Int {
|
||||
if (index >= lines.size) return -1
|
||||
val bytes = lines[index].toByteArray(Charsets.UTF_8)
|
||||
val n = minOf(len, bytes.size)
|
||||
System.arraycopy(bytes, 0, b, off, n)
|
||||
index++
|
||||
return n
|
||||
}
|
||||
}
|
||||
return GroupsParser.parseStream(stream)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dropsLastDigitFromPeriodId() {
|
||||
val html = page(
|
||||
facultyDiv(faculty),
|
||||
groupDiv("123456", "ИВТб-3304-05-00 (бакалавриат)")
|
||||
)
|
||||
|
||||
val group = parse(html).groups.single()
|
||||
|
||||
assertEquals("12345", group.id)
|
||||
assertEquals("ИВТб-3304-05-00", group.name)
|
||||
assertEquals(facultyShort, group.faculty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun removesParenthesizedSuffixFromGroupName() {
|
||||
val html = page(facultyDiv(faculty), groupDiv("777", "Группа А (подгруппа)"))
|
||||
|
||||
val group = parse(html).groups.single()
|
||||
|
||||
assertEquals("77", group.id)
|
||||
assertEquals("Группа А", group.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keepsFacultyNameWhenNoShorthand() {
|
||||
val html = page(facultyDiv("Некий институт (ОРУ)"), groupDiv("5555", "Гр"))
|
||||
|
||||
val group = parse(html).groups.single()
|
||||
|
||||
assertEquals("Некий институт", group.faculty)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun facultyAppliesToGroupsUntilChanged() {
|
||||
val html = page(
|
||||
facultyDiv(faculty),
|
||||
groupDiv("1111", "А-1"),
|
||||
groupDiv("2222", "А-2"),
|
||||
facultyDiv("Юридический институт (факультет) (ОРУ)"),
|
||||
groupDiv("3333", "Ю-1")
|
||||
)
|
||||
|
||||
val faculties = parse(html).groups.map { it.faculty }
|
||||
|
||||
assertEquals(
|
||||
listOf(facultyShort, facultyShort, "ЮИ (Юридический институт)"),
|
||||
faculties
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parsesDateRangesGroupedBySeason() {
|
||||
val html = page(
|
||||
pdfLink("/reports/schedule/Group/12345_1_14092026_27092026.pdf"),
|
||||
pdfLink("/reports/schedule/Group/12345_1_28092026_11102026.pdf"),
|
||||
pdfLink("/reports/schedule/Group/12345_2_09022027_22022027.pdf")
|
||||
)
|
||||
|
||||
val ranges = parse(html).dateRanges
|
||||
|
||||
assertEquals(
|
||||
listOf(DateRange("14092026", "27092026"), DateRange("28092026", "11102026")),
|
||||
ranges["12345"]?.get(Season.AUTUMN)
|
||||
)
|
||||
assertEquals(
|
||||
listOf(DateRange("09022027", "22022027")),
|
||||
ranges["12345"]?.get(Season.SPRING)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ignoresUnrelatedLinks() {
|
||||
val html = page(
|
||||
pdfLink("/other/12345_1_14092026_27092026.pdf"),
|
||||
pdfLink("/reports/schedule/Group/123_1_14092026_27092026.pdf")
|
||||
)
|
||||
|
||||
assertTrue(parse(html).dateRanges.isEmpty())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun extractsRangesFromTextBeforeTag() {
|
||||
val html = "/reports/schedule/Group/12345_1_14092026_27092026.pdf " + pdfLink("#")
|
||||
|
||||
val ranges = parse(html).dateRanges
|
||||
|
||||
assertEquals(
|
||||
listOf(DateRange("14092026", "27092026")),
|
||||
ranges["12345"]?.get(Season.AUTUMN)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun greaterThanInsideQuotedAttributeDoesNotBreakTag() {
|
||||
val html = page(
|
||||
facultyDiv(faculty),
|
||||
"""<div class="grpPeriod" data-grp_period_id="555" title="a>b">Группа Б</div>"""
|
||||
)
|
||||
|
||||
val group = parse(html).groups.single()
|
||||
|
||||
assertEquals("55", group.id)
|
||||
assertEquals("Группа Б", group.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun multipleReadsProduceSameResultAsSingleRead() {
|
||||
val html = page(
|
||||
facultyDiv(faculty),
|
||||
groupDiv("123456", "ИВТб-3304-05-00 (бакалавриат)"),
|
||||
pdfLink("/reports/schedule/Group/12345_1_14092026_27092026.pdf"),
|
||||
groupDiv("7654321", "ИВТб-3304-05-01"),
|
||||
pdfLink("/reports/schedule/Group/765432_2_09022027_22022027.pdf")
|
||||
)
|
||||
|
||||
assertEquals(parse(html), parseLineByLine(html))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package agzam.vyatsu.data.remote
|
||||
|
||||
import agzam.vyatsu.domain.model.DateRange
|
||||
import agzam.vyatsu.domain.model.Season
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class VyatsuUrlsTest {
|
||||
|
||||
private val autumnRange = DateRange("14092026", "27092026")
|
||||
|
||||
@Test
|
||||
fun groupPdfUrl_usesAutumnKey() {
|
||||
assertEquals(
|
||||
"https://www.vyatsu.ru/reports/schedule/Group/12345_1_14092026_27092026.pdf",
|
||||
VyatsuUrls.groupPdfUrl("12345", Season.AUTUMN, autumnRange)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun groupPdfUrl_usesSpringKey() {
|
||||
assertEquals(
|
||||
"https://www.vyatsu.ru/reports/schedule/Group/765432_2_09022027_22022027.pdf",
|
||||
VyatsuUrls.groupPdfUrl("765432", Season.SPRING, DateRange("09022027", "22022027"))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun season_apiKeysMatchUrlScheme() {
|
||||
assertEquals("1", Season.AUTUMN.apiKey)
|
||||
assertEquals("2", Season.SPRING.apiKey)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun generatedUrl_isRecognizedByGroupsParser() {
|
||||
val url = VyatsuUrls.groupPdfUrl("12345", Season.AUTUMN, autumnRange)
|
||||
val html = """<a href="$url">x</a>"""
|
||||
|
||||
val parsed = GroupsParser.parseStream(html.byteInputStream(Charsets.UTF_8))
|
||||
|
||||
assertEquals(listOf(autumnRange), parsed.dateRanges["12345"]?.get(Season.AUTUMN))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package agzam.vyatsu.domain
|
||||
|
||||
import agzam.vyatsu.domain.model.Season
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import java.time.LocalDate
|
||||
import java.time.format.DateTimeParseException
|
||||
|
||||
class ScheduleDatesTest {
|
||||
|
||||
private val firstMonday = "14092026"
|
||||
|
||||
@Test
|
||||
fun asDate_parsesDdMmYyyy() {
|
||||
assertEquals(LocalDate.of(2026, 9, 14), ScheduleDates.asDate("14092026"))
|
||||
assertEquals(LocalDate.of(2026, 9, 1), ScheduleDates.asDate("01092026"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun asDate_wrongFormat_throws() {
|
||||
assertThrows(DateTimeParseException::class.java) {
|
||||
ScheduleDates.asDate("2026-09-14")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dayIndexes_mondayOfFirstWeek_isZeroZero() {
|
||||
assertEquals(0 to 0, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 14)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dayIndexes_tuesdayToSaturday_areSameWeek() {
|
||||
assertEquals(0 to 1, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 15)))
|
||||
assertEquals(0 to 2, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 16)))
|
||||
assertEquals(0 to 3, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 17)))
|
||||
assertEquals(0 to 4, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 18)))
|
||||
assertEquals(0 to 5, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 19)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dayIndexes_sunday_movesToNextMonday() {
|
||||
assertEquals(1 to 0, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 20)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dayIndexes_secondWeek() {
|
||||
assertEquals(1 to 0, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 21)))
|
||||
assertEquals(1 to 1, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 22)))
|
||||
assertEquals(1 to 5, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 26)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dayIndexes_thirdCalendarWeek_wrapsToFirstScheduleWeek() {
|
||||
assertEquals(0 to 0, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 28)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dayIndexes_dateBeforeFirstMonday_isSecondScheduleWeek() {
|
||||
assertEquals(1 to 0, ScheduleDates.dayIndexes(firstMonday, LocalDate.of(2026, 9, 7)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun dateByIndexes_composesFromFirstDate() {
|
||||
assertEquals(LocalDate.of(2026, 9, 14), ScheduleDates.dateByIndexes(firstMonday, 0, 0))
|
||||
assertEquals(LocalDate.of(2026, 9, 15), ScheduleDates.dateByIndexes(firstMonday, 0, 1))
|
||||
assertEquals(LocalDate.of(2026, 9, 21), ScheduleDates.dateByIndexes(firstMonday, 1, 0))
|
||||
assertEquals(LocalDate.of(2026, 9, 27), ScheduleDates.dateByIndexes(firstMonday, 1, 6))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentSeason_augustToDecember_isAutumn() {
|
||||
assertEquals(Season.AUTUMN, ScheduleDates.currentSeason(LocalDate.of(2026, 8, 1)))
|
||||
assertEquals(Season.AUTUMN, ScheduleDates.currentSeason(LocalDate.of(2026, 12, 31)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun currentSeason_januaryToJuly_isSpring() {
|
||||
assertEquals(Season.SPRING, ScheduleDates.currentSeason(LocalDate.of(2026, 1, 1)))
|
||||
assertEquals(Season.SPRING, ScheduleDates.currentSeason(LocalDate.of(2026, 7, 31)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package agzam.vyatsu.domain.model
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class LessonTypeTest {
|
||||
|
||||
@Test
|
||||
fun fromRaw_recognizesExactLabels() {
|
||||
assertEquals(LessonType.LECTURE, LessonType.fromRaw("Лекция"))
|
||||
assertEquals(LessonType.PRACTICE, LessonType.fromRaw("Практическое занятие"))
|
||||
assertEquals(LessonType.LABORATORY, LessonType.fromRaw("Лабораторная работа"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromRaw_trimsSurroundingWhitespace() {
|
||||
assertEquals(LessonType.LECTURE, LessonType.fromRaw(" Лекция "))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromRaw_unknownValue_isOther() {
|
||||
assertEquals(LessonType.OTHER, LessonType.fromRaw("Семинар"))
|
||||
assertEquals(LessonType.OTHER, LessonType.fromRaw(""))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun labels_matchParserVocabulary() {
|
||||
assertEquals("Лекция", LessonType.LECTURE.label)
|
||||
assertEquals("Практическое занятие", LessonType.PRACTICE.label)
|
||||
assertEquals("Лабораторная работа", LessonType.LABORATORY.label)
|
||||
assertEquals("Занятие", LessonType.OTHER.label)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fromRaw_roundTripsEveryLabel() {
|
||||
for (type in LessonType.entries) {
|
||||
assertEquals(type, LessonType.fromRaw(type.label))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package agzam.vyatsu.domain.model
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Test
|
||||
import java.time.LocalDate
|
||||
|
||||
class ScheduleTest {
|
||||
|
||||
private val autumnPeriod = SchedulePeriod(
|
||||
weekStart = LocalDate.of(2026, 9, 14),
|
||||
weekEnd = LocalDate.of(2026, 9, 27),
|
||||
season = Season.AUTUMN
|
||||
)
|
||||
private val nextPeriod = SchedulePeriod(
|
||||
weekStart = LocalDate.of(2026, 9, 28),
|
||||
weekEnd = LocalDate.of(2026, 10, 11),
|
||||
season = Season.AUTUMN
|
||||
)
|
||||
private val dayInFirstPeriod = Day(LocalDate.of(2026, 9, 15), emptyList())
|
||||
private val dayInSecondPeriod = Day(LocalDate.of(2026, 9, 29), emptyList())
|
||||
private val dayOutsidePeriods = Day(LocalDate.of(2026, 10, 20), emptyList())
|
||||
|
||||
private val schedule = Schedule(
|
||||
groupId = "12345",
|
||||
periods = listOf(autumnPeriod, nextPeriod),
|
||||
days = listOf(dayInFirstPeriod, dayInSecondPeriod, dayOutsidePeriods)
|
||||
)
|
||||
|
||||
@Test
|
||||
fun periodOf_matchesDateInsidePeriod() {
|
||||
assertEquals(autumnPeriod, schedule.periodOf(LocalDate.of(2026, 9, 15)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun periodOf_includesBothBounds() {
|
||||
assertEquals(autumnPeriod, schedule.periodOf(autumnPeriod.weekStart))
|
||||
assertEquals(autumnPeriod, schedule.periodOf(autumnPeriod.weekEnd))
|
||||
assertEquals(nextPeriod, schedule.periodOf(nextPeriod.weekStart))
|
||||
assertEquals(nextPeriod, schedule.periodOf(nextPeriod.weekEnd))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun periodOf_returnsNullForDateOutsideAllPeriods() {
|
||||
assertNull(schedule.periodOf(LocalDate.of(2026, 10, 20)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun daysOf_returnsOnlyDaysWithinPeriod() {
|
||||
assertEquals(listOf(dayInFirstPeriod), schedule.daysOf(autumnPeriod))
|
||||
assertEquals(listOf(dayInSecondPeriod), schedule.daysOf(nextPeriod))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun daysOf_returnsEmptyWhenPeriodHasNoDays() {
|
||||
val emptyPeriod = SchedulePeriod(
|
||||
weekStart = LocalDate.of(2027, 1, 4),
|
||||
weekEnd = LocalDate.of(2027, 1, 17),
|
||||
season = Season.SPRING
|
||||
)
|
||||
|
||||
assertEquals(emptyList<Day>(), schedule.daysOf(emptyPeriod))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun daysOf_neverReturnsDayOutsideEveryPeriod() {
|
||||
val collected = schedule.periods.flatMap { schedule.daysOf(it) }
|
||||
|
||||
assertFalse(collected.contains(dayOutsidePeriods))
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ agp = "9.1.1"
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
junitVersion = "1.1.5"
|
||||
espressoCore = "3.5.1"
|
||||
runner = "1.5.2"
|
||||
lifecycleRuntimeKtx = "2.6.1"
|
||||
activityCompose = "1.8.0"
|
||||
kotlin = "2.2.10"
|
||||
@@ -22,7 +22,7 @@ monitor = "1.8.0"
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
|
||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||
@@ -30,8 +30,6 @@ androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
|
||||
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||
|
||||
Reference in New Issue
Block a user