test: check props

This commit is contained in:
Nurul Huda (Apon) 2026-01-24 00:01:19 +06:00
parent cffa94146e
commit e42982deda
2 changed files with 58 additions and 23 deletions

View file

@ -2,7 +2,7 @@ const std = @import("std");
const Instant = @This(); const Instant = @This();
inner: *CInstant, _inner: *CInstant,
epochMilliseconds: i64, epochMilliseconds: i64,
epochNanoseconds: i128, epochNanoseconds: i128,
@ -36,37 +36,37 @@ fn fromUtf16(text: []const u16) !Instant {
/// Add a Duration to this instant (Temporal.Instant.prototype.add). /// Add a Duration to this instant (Temporal.Instant.prototype.add).
pub fn add(self: Instant, duration: *const Duration) !Instant { pub fn add(self: Instant, duration: *const Duration) !Instant {
return wrapInstant(temporal_rs_Instant_add(self.inner, duration)); return wrapInstant(temporal_rs_Instant_add(self._inner, duration));
} }
/// Subtract a Duration from this instant (Temporal.Instant.prototype.subtract). /// Subtract a Duration from this instant (Temporal.Instant.prototype.subtract).
pub fn subtract(self: Instant, duration: *const Duration) !Instant { pub fn subtract(self: Instant, duration: *const Duration) !Instant {
return wrapInstant(temporal_rs_Instant_subtract(self.inner, duration)); return wrapInstant(temporal_rs_Instant_subtract(self._inner, duration));
} }
/// Difference until another instant (Temporal.Instant.prototype.until). /// Difference until another instant (Temporal.Instant.prototype.until).
pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { pub fn until(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle {
return wrapDuration(temporal_rs_Instant_until(self.inner, other.inner, settings)); return wrapDuration(temporal_rs_Instant_until(self._inner, other._inner, settings));
} }
/// Difference since another instant (Temporal.Instant.prototype.since). /// Difference since another instant (Temporal.Instant.prototype.since).
pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle { pub fn since(self: Instant, other: Instant, settings: DifferenceSettings) !DurationHandle {
return wrapDuration(temporal_rs_Instant_since(self.inner, other.inner, settings)); return wrapDuration(temporal_rs_Instant_since(self._inner, other._inner, settings));
} }
/// Round this instant (Temporal.Instant.prototype.round). /// Round this instant (Temporal.Instant.prototype.round).
pub fn round(self: Instant, options: RoundingOptions) !Instant { pub fn round(self: Instant, options: RoundingOptions) !Instant {
return wrapInstant(temporal_rs_Instant_round(self.inner, options)); return wrapInstant(temporal_rs_Instant_round(self._inner, options));
} }
/// Compare two instants (Temporal.Instant.compare). /// Compare two instants (Temporal.Instant.compare).
pub fn compare(a: Instant, b: Instant) i8 { pub fn compare(a: Instant, b: Instant) i8 {
return temporal_rs_Instant_compare(a.inner, b.inner); return temporal_rs_Instant_compare(a._inner, b._inner);
} }
/// Equality check (Temporal.Instant.prototype.equals). /// Equality check (Temporal.Instant.prototype.equals).
pub fn equals(a: Instant, b: Instant) bool { pub fn equals(a: Instant, b: Instant) bool {
return temporal_rs_Instant_equals(a.inner, b.inner); return temporal_rs_Instant_equals(a._inner, b._inner);
} }
/// Epoch milliseconds accessor (Temporal.Instant.prototype.epochMilliseconds). /// Epoch milliseconds accessor (Temporal.Instant.prototype.epochMilliseconds).
@ -92,7 +92,7 @@ pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptio
const writer = diplomat_buffer_write_create(128); const writer = diplomat_buffer_write_create(128);
defer diplomat_buffer_write_destroy(writer); defer diplomat_buffer_write_destroy(writer);
const res = temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self.inner, zone_opt, rounding, writer); const res = temporal_rs_Instant_to_ixdtf_string_with_compiled_data(self._inner, zone_opt, rounding, writer);
try handleVoidResult(res); try handleVoidResult(res);
const len = diplomat_buffer_write_len(writer); const len = diplomat_buffer_write_len(writer);
@ -115,7 +115,7 @@ fn toStringWithProvider(self: Instant, allocator: std.mem.Allocator, provider: *
const writer = diplomat_buffer_write_create(128); const writer = diplomat_buffer_write_create(128);
defer diplomat_buffer_write_destroy(writer); defer diplomat_buffer_write_destroy(writer);
const res = temporal_rs_Instant_to_ixdtf_string_with_provider(self.inner, zone_opt, rounding, provider, writer); const res = temporal_rs_Instant_to_ixdtf_string_with_provider(self._inner, zone_opt, rounding, provider, writer);
try handleVoidResult(res); try handleVoidResult(res);
const len = diplomat_buffer_write_len(writer); const len = diplomat_buffer_write_len(writer);
@ -140,22 +140,22 @@ pub fn toLocaleString(self: Instant, allocator: std.mem.Allocator) ![]u8 {
/// Convert to ZonedDateTime using built-in provider (Temporal.Instant.prototype.toZonedDateTimeISO). /// Convert to ZonedDateTime using built-in provider (Temporal.Instant.prototype.toZonedDateTimeISO).
pub fn toZonedDateTimeISO(self: Instant, zone: TimeZone) !ZonedDateTimeHandle { pub fn toZonedDateTimeISO(self: Instant, zone: TimeZone) !ZonedDateTimeHandle {
return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso(self.inner, zone)); return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso(self._inner, zone));
} }
/// Convert to ZonedDateTime using an explicit provider. /// Convert to ZonedDateTime using an explicit provider.
fn toZonedDateTimeIsoWithProvider(self: Instant, zone: TimeZone, provider: *const Provider) !ZonedDateTimeHandle { fn toZonedDateTimeIsoWithProvider(self: Instant, zone: TimeZone, provider: *const Provider) !ZonedDateTimeHandle {
return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self.inner, zone, provider)); return wrapZonedDateTime(temporal_rs_Instant_to_zoned_date_time_iso_with_provider(self._inner, zone, provider));
} }
/// Clone the underlying instant. /// Clone the underlying instant.
fn clone(self: Instant) Instant { fn clone(self: Instant) Instant {
const ptr = temporal_rs_Instant_clone(self.inner); const ptr = temporal_rs_Instant_clone(self._inner);
return .{ .inner = ptr, .epochMilliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epochNanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) }; return .{ ._inner = ptr, .epochMilliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epochNanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) };
} }
pub fn deinit(self: Instant) void { pub fn deinit(self: Instant) void {
temporal_rs_Instant_destroy(self.inner); temporal_rs_Instant_destroy(self._inner);
} }
// --- Helpers ----------------------------------------------------------------- // --- Helpers -----------------------------------------------------------------
@ -163,7 +163,7 @@ pub fn deinit(self: Instant) void {
fn wrapInstant(res: InstantResult) !Instant { fn wrapInstant(res: InstantResult) !Instant {
if (!res.is_ok) return error.TemporalError; if (!res.is_ok) return error.TemporalError;
const ptr = res.result.ok orelse return error.TemporalError; const ptr = res.result.ok orelse return error.TemporalError;
return .{ .inner = ptr, .epochMilliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epochNanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) }; return .{ ._inner = ptr, .epochMilliseconds = temporal_rs_Instant_epoch_milliseconds(ptr), .epochNanoseconds = partsToI128(temporal_rs_Instant_epoch_nanoseconds(ptr)) };
} }
fn wrapDuration(res: DurationResult) !DurationHandle { fn wrapDuration(res: DurationResult) !DurationHandle {

View file

@ -93,13 +93,13 @@ test "Temporal.Instant" {
"toJSON", "toJSON",
"toLocaleString", "toLocaleString",
"toString", "toString",
"toZonedDateTimeISO", // Temporal.Instant.toZonedDateTimeISO "toZonedDateTimeISO",
"until", "until",
"valueOf", "valueOf",
// Properties // Properties
// "epochMilliseconds", "epochMilliseconds",
// "epochNanoseconds", "epochNanoseconds",
}; };
try assertDecls(Instant, checks); try assertDecls(Instant, checks);
@ -403,22 +403,38 @@ fn assertDecls(comptime T: type, checks: anytype) !void {
const std = @import("std"); const std = @import("std");
const typeInfo = @typeInfo(T); const typeInfo = @typeInfo(T);
// Check: all items in checks exist // Check: all items in checks exist (either as decls or as fields)
inline for (checks) |check| { inline for (checks) |check| {
const should_ignore = const should_ignore =
std.mem.eql(u8, check, "deinit") or std.mem.eql(u8, check, "deinit") or
std.mem.eql(u8, check, "valueOf"); std.mem.eql(u8, check, "valueOf");
const has = @hasDecl(T, check);
if (!should_ignore) { if (!should_ignore) {
if (!has) std.log.err("Missing {s} decl: {s}", .{ @typeName(T), check }); const hasDecl = @hasDecl(T, check);
// Also check if it's a field (property)
var hasField = false;
if (typeInfo == .@"struct") {
const struct_info = typeInfo.@"struct";
inline for (struct_info.fields) |field| {
if (std.mem.eql(u8, field.name, check)) {
hasField = true;
break;
}
}
}
const has = hasDecl or hasField;
if (!has) std.log.err("Missing {s} decl or field: {s}", .{ @typeName(T), check });
try std.testing.expect(has); try std.testing.expect(has);
} }
} }
// Check: no extraneous declarations beyond checks // Check: no extraneous declarations or fields beyond checks
if (typeInfo == .@"struct") { if (typeInfo == .@"struct") {
const struct_info = typeInfo.@"struct"; const struct_info = typeInfo.@"struct";
// Check declarations
inline for (struct_info.decls) |decl| { inline for (struct_info.decls) |decl| {
// Allow deinit as extraneous // Allow deinit as extraneous
if (comptime std.mem.eql(u8, decl.name, "deinit")) continue; if (comptime std.mem.eql(u8, decl.name, "deinit")) continue;
@ -436,5 +452,24 @@ fn assertDecls(comptime T: type, checks: anytype) !void {
try std.testing.expect(false); try std.testing.expect(false);
} }
} }
// Check fields (properties)
inline for (struct_info.fields) |field| {
// Allow internal fields (starting with underscore)
if (comptime std.mem.startsWith(u8, field.name, "_")) continue;
var found = false;
inline for (checks) |check| {
if (std.mem.eql(u8, field.name, check)) {
found = true;
break;
}
}
if (!found) {
std.log.err("Extraneous {s} field: {s}", .{ @typeName(T), field.name });
try std.testing.expect(false);
}
}
} }
} }