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();
inner: *CInstant,
_inner: *CInstant,
epochMilliseconds: i64,
epochNanoseconds: i128,
@ -36,37 +36,37 @@ fn fromUtf16(text: []const u16) !Instant {
/// Add a Duration to this instant (Temporal.Instant.prototype.add).
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).
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).
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).
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).
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).
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).
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).
@ -92,7 +92,7 @@ pub fn toString(self: Instant, allocator: std.mem.Allocator, opts: ToStringOptio
const writer = diplomat_buffer_write_create(128);
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);
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);
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);
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).
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.
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.
fn clone(self: Instant) Instant {
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)) };
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)) };
}
pub fn deinit(self: Instant) void {
temporal_rs_Instant_destroy(self.inner);
temporal_rs_Instant_destroy(self._inner);
}
// --- Helpers -----------------------------------------------------------------
@ -163,7 +163,7 @@ pub fn deinit(self: Instant) void {
fn wrapInstant(res: InstantResult) !Instant {
if (!res.is_ok) 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 {

View file

@ -93,13 +93,13 @@ test "Temporal.Instant" {
"toJSON",
"toLocaleString",
"toString",
"toZonedDateTimeISO", // Temporal.Instant.toZonedDateTimeISO
"toZonedDateTimeISO",
"until",
"valueOf",
// Properties
// "epochMilliseconds",
// "epochNanoseconds",
"epochMilliseconds",
"epochNanoseconds",
};
try assertDecls(Instant, checks);
@ -403,22 +403,38 @@ fn assertDecls(comptime T: type, checks: anytype) !void {
const std = @import("std");
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| {
const should_ignore =
std.mem.eql(u8, check, "deinit") or
std.mem.eql(u8, check, "valueOf");
const has = @hasDecl(T, check);
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);
}
}
// Check: no extraneous declarations beyond checks
// Check: no extraneous declarations or fields beyond checks
if (typeInfo == .@"struct") {
const struct_info = typeInfo.@"struct";
// Check declarations
inline for (struct_info.decls) |decl| {
// Allow deinit as extraneous
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);
}
}
// 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);
}
}
}
}