valuesTests method

void valuesTests()

Implementation

void valuesTests() {
  group('Values', () {
    late Cursor cursor;

    setUp(() async {
      connection = await connector.connect(_initDb);
      cursor = await connection.cursor();
    });

    tearDown(() async {
      await connection.close();
    });

    group('query on different types', () {
      group('equality', () {
        test('basic values', () async {
          await insertRow(
            connection,
            "1",
            name: "Christopher Schwartz",
            age: 56,
            height: 1.81,
            isStaff: true,
            createdAt: DateTime.utc(2025, 4, 2, 13, 23),
          );

          final testCases = [
            (property: "name", value: StringValue("Christopher Schwartz")),
            (property: "age", value: NumericValue(56)),
            (property: "height", value: NumericValue(1.81)),
            (property: "isStaff", value: BooleanValue(true)),
            (
              property: "createdAt",
              value: DateTimeValue(DateTime.utc(2025, 4, 2, 13, 23))
            ),
            (property: "profilePicture", value: NullValue()),
          ];

          for (final testcase in testCases) {
            await cursor.execute(
              Query(
                from: schema,
                where: SingleFilter(
                  left: Property(testcase.property),
                  operator: Operator.equals,
                  right: testcase.value,
                ),
              ),
            );
            final res = await cursor.fetchall();
            expect(res, hasLength(1),
                reason: "Failed equality comparison on $testcase");
          }
        });
      });

      group('inequality', () {
        test('basic values', () async {
          await insertRow(
            connection,
            "1",
            name: "Christopher Schwartz",
            age: 56,
            height: 1.81,
            isStaff: true,
            createdAt: DateTime.utc(2025, 4, 2, 13, 23),
          );

          final testCases = [
            (property: "name", value: StringValue("Christopher Schwartz")),
            (property: "age", value: NumericValue(56)),
            (property: "height", value: NumericValue(1.81)),
            (property: "isStaff", value: BooleanValue(true)),
            (
              property: "createdAt",
              value: DateTimeValue(DateTime.utc(2025, 4, 2, 13, 23))
            ),
            (property: "profilePicture", value: NullValue()),
          ];

          for (final testcase in testCases) {
            await cursor.execute(
              Query(
                from: schema,
                where: SingleFilter(
                  left: Property(testcase.property),
                  operator: Operator.notEquals,
                  right: testcase.value,
                ),
              ),
            );
            final res = await cursor.fetchall();
            expect(res, isEmpty,
                reason: "Failed equality comparison on $testcase");
          }
        });
      });

      group('greater than', () {
        test('basic values', () async {
          await insertRow(
            connection,
            "1",
            name: "Christopher Schwartz",
            age: 56,
            height: 1.81,
            isStaff: true,
            createdAt: DateTime.utc(2025, 4, 2, 13, 23),
          );

          final testCases = [
            (property: "name", value: StringValue("Bhristopher Schwartz")),
            (property: "age", value: NumericValue(-1)),
            (property: "height", value: NumericValue(1.80)),
            (
              property: "createdAt",
              value: DateTimeValue(DateTime.utc(2025, 4, 2, 13, 22)),
            ),
          ];

          for (final testcase in testCases) {
            await cursor.execute(
              Query(
                from: schema,
                where: SingleFilter(
                  left: Property(testcase.property),
                  operator: Operator.greaterThan,
                  right: testcase.value,
                ),
              ),
            );
            final res = await cursor.fetchall();
            expect(res, hasLength(1),
                reason: "Failed equality comparison on $testcase");
          }
        });
      });

      group('list operations', () {
        test('in list', () async {
          await insertRow(
            connection,
            "1",
            name: "Christopher Schwartz",
            age: 56,
            height: 1.81,
            isStaff: true,
            createdAt: DateTime.utc(2025, 4, 2, 13, 23),
          );

          final testCases = [
            (
              property: "name",
              value: ListValue(
                [
                  StringValue("Bhristopher Schwartz"),
                  StringValue("Christopher Schwartz")
                ],
              )
            ),
            (
              property: "age",
              value: ListValue(
                [
                  NumericValue(56),
                  NumericValue(-1),
                  NumericValue(86),
                ],
              ),
            ),
            (
              property: "height",
              value: ListValue([
                NumericValue(1),
                NumericValue(1.01),
                NumericValue(1.81),
              ]),
            ),
          ];

          for (final testcase in testCases) {
            await cursor.execute(
              Query(
                from: schema,
                where: SingleFilter(
                  left: Property(testcase.property),
                  operator: Operator.isIn,
                  right: testcase.value,
                ),
              ),
            );
            var res = await cursor.fetchall();
            expect(res, hasLength(1),
                reason: "Failed 'in' comparison on $testcase");
          }
        });

        test('not in list', () async {
          await insertRow(
            connection,
            "1",
            name: "Christopher Schwartz",
            age: 56,
            height: 1.81,
            isStaff: true,
            createdAt: DateTime.utc(2025, 4, 2, 13, 23),
          );

          final testCases = [
            (
              property: "name",
              value: ListValue(
                [
                  StringValue("Bhristopher Schwartz"),
                  StringValue("Christopher Schwartz")
                ],
              )
            ),
            (
              property: "age",
              value: ListValue(
                [
                  NumericValue(56),
                  NumericValue(-1),
                  NumericValue(86),
                ],
              ),
            ),
            (
              property: "height",
              value: ListValue([
                NumericValue(1),
                NumericValue(1.01),
                NumericValue(1.81),
              ]),
            ),
          ];

          for (final testcase in testCases) {
            await cursor.execute(
              Query(
                from: schema,
                where: SingleFilter(
                  left: Property(testcase.property),
                  operator: Operator.isNotIn,
                  right: testcase.value,
                ),
              ),
            );
            var res = await cursor.fetchall();
            expect(res, isEmpty,
                reason: "Failed 'in' comparison on $testcase");
          }
        });
      });

      // What is the correct semantics of this?
      // In indexeddb, checking <= boolean is ok, and actually
      // needed as we transform a != true to a < true || a > true, which
      // then becomes a < 1 || a > 1 - which is completely legit.
      test('illegal operations', () async {
        await insertRow(
          connection,
          "1",
          name: "Christopher Schwartz",
          age: 56,
          height: 1.81,
          isStaff: true,
          createdAt: DateTime.utc(2025, 4, 2, 13, 23),
        );

        final testCases = [
          (
            property: "isStaff",
            value: BooleanValue(false),
            operator: Operator.greaterThan
          ),
          (
            property: "isStaff",
            value: BooleanValue(false),
            operator: Operator.greaterThanOrEqual
          ),
          (
            property: "isStaff",
            value: BooleanValue(false),
            operator: Operator.lessThan
          ),
          (
            property: "isStaff",
            value: BooleanValue(false),
            operator: Operator.lessThanOrEqual
          ),
          (
            property: "isStaff",
            value: BooleanValue(false),
            operator: Operator.isIn
          ),
          (
            property: "isStaff",
            value: BooleanValue(false),
            operator: Operator.isNotIn
          ),
          (
            property: "profilePicture",
            value: NullValue(),
            operator: Operator.greaterThan
          ),
          (
            property: "profilePicture",
            value: NullValue(),
            operator: Operator.greaterThanOrEqual
          ),
          (
            property: "profilePicture",
            value: NullValue(),
            operator: Operator.lessThan
          ),
          (
            property: "profilePicture",
            value: NullValue(),
            operator: Operator.lessThanOrEqual
          ),
          (
            property: "profilePicture",
            value: NullValue(),
            operator: Operator.isIn
          ),
          (
            property: "profilePicture",
            value: NullValue(),
            operator: Operator.isNotIn
          ),
        ];

        for (final testcase in testCases) {
          expect(
              () => cursor.execute(
                    Query(
                      from: schema,
                      where: SingleFilter(
                        left: Property(testcase.property),
                        operator: testcase.operator,
                        right: testcase.value,
                      ),
                    ),
                  ),
              throwsA(
                isA<ProgrammingError>(),
              ),
              reason: "Operation did not fail: $testcase");
        }
      }, skip: "Skipped as the semantics here are unclear");
    });

    group('basic types', () {
      test('null values', () async {
        await insertRow(connection, "1");

        await cursor.execute(Query(from: schema));

        final res = await cursor.fetchall();
        expect(res[0], {
          "id": "1",
          "name": null,
          "age": null,
          "height": null,
          "isStaff": null,
          "createdAt": null,
          "profilePicture": null,
        });
      });

      test('non-null values', () async {
        final picture = ByteData(100);
        final bytesList = List.generate(100, (i) => i % 10);
        picture.buffer.asUint8List().setAll(0, bytesList);
        await insertRow(
          connection,
          "1",
          name: "Christopher Schwartz",
          age: 56,
          height: 1.81,
          isStaff: true,
          createdAt: DateTime.utc(2025, 4, 2, 13, 23),
          profilePicture: picture,
        );

        await cursor.execute(Query(from: schema));

        final res = await cursor.fetchall();

        expect((res[0]["profilePicture"] as ByteData).buffer.asUint8List(),
            bytesList);

        res[0].remove("profilePicture");

        expect(res[0], {
          "id": "1",
          "name": "Christopher Schwartz",
          "age": 56,
          "height": 1.81,
          "isStaff": true,
          "createdAt": DateTime.utc(2025, 4, 2, 13, 23),
        });
      });

      test('insert fails if value of the wrong type', () async {
        expect(
            () => cursor.execute(Insert(schema, [
                  {
                    "id": 1,
                    "name": 23,
                    "age": null,
                    "height": null,
                    "isStaff": null,
                    "createdAt": null,
                    "profilePicture": null,
                  }
                ])),
            throwsA(isA<DataError>()));
      });

      test('Query returns null if value cannot be converted', () async {
        await insertRow(
          connection,
          "1",
          name: "Tomas Chippendale",
        );

        final wrongSchema = schema.copyWith(
          properties: [
            PropertySchema(
              "id",
              type: PropertyType.text,
              primaryKey: true,
              nullable: false,
            ),
            // pretend "name" is a DateTime
            PropertySchema("name", type: PropertyType.datetime),
            PropertySchema("age", type: PropertyType.integer),
            PropertySchema("height", type: PropertyType.double),
            PropertySchema("isStaff", type: PropertyType.boolean),
            PropertySchema("createdAt", type: PropertyType.datetime),
            PropertySchema("profilePicture", type: PropertyType.blob),
          ],
        );

        await cursor.execute(Query(from: wrongSchema));

        final res = await cursor.fetchone();
        expect(res!['name'], isNull);
      });

      test('Query returns unconverted value if not in schema', () async {
        await insertRow(
          connection,
          "1",
          name: "Tomas Chippendale",
          createdAt: DateTime(1982, 9, 1),
        );

        final wrongSchema = schema.copyWith(
          properties: [
            PropertySchema(
              "id",
              type: PropertyType.text,
              primaryKey: true,
              nullable: false,
            ),

            PropertySchema("name", type: PropertyType.text),
            PropertySchema("age", type: PropertyType.integer),
            PropertySchema("height", type: PropertyType.double),
            PropertySchema("isStaff", type: PropertyType.boolean),
            // "createdAt" has been removed from schema after insert
            // PropertySchema("createdAt", type: PropertyType.datetime),
            PropertySchema("profilePicture", type: PropertyType.blob),
          ],
        );

        await cursor.execute(Query(from: wrongSchema));

        final res = await cursor.fetchone();
        expect(res!['createdAt'], isNotNull);
        expect(res['createdAt'], isA<String>());
      });

      test('DateTimes are converted to UTC', () async {
        await insertRow(
          connection,
          "1",
          createdAt: DateTime.parse("2025-04-02T14:23:00+0100"),
        );

        await cursor.execute(Query(from: schema));

        final res = await cursor.fetchall();
        expect(res[0], {
          "id": "1",
          "name": null,
          "age": null,
          "height": null,
          "isStaff": null,
          "createdAt": DateTime.utc(2025, 4, 2, 13, 23),
          "profilePicture": null,
        });
      });
    });
  });
}