@@ -4,6 +4,22 @@ import { afterEach, describe, expect, it, vi } from "vitest";
44import MembershipRequestDetailPage from "../MembershipRequestDetailPage.vue" ;
55import type { MembershipRequestDetailBootstrap } from "../types" ;
66
7+ function expectedLocalTimestamp ( value : string ) : string {
8+ const parsed = new Date ( value ) ;
9+ const year = String ( parsed . getFullYear ( ) ) ;
10+ const month = String ( parsed . getMonth ( ) + 1 ) . padStart ( 2 , "0" ) ;
11+ const day = String ( parsed . getDate ( ) ) . padStart ( 2 , "0" ) ;
12+ const hour = String ( parsed . getHours ( ) ) . padStart ( 2 , "0" ) ;
13+ const minute = String ( parsed . getMinutes ( ) ) . padStart ( 2 , "0" ) ;
14+ const timezoneOffsetMinutes = - parsed . getTimezoneOffset ( ) ;
15+ const offsetSign = timezoneOffsetMinutes >= 0 ? "+" : "-" ;
16+ const absoluteOffsetMinutes = Math . abs ( timezoneOffsetMinutes ) ;
17+ const offsetHours = String ( Math . floor ( absoluteOffsetMinutes / 60 ) ) . padStart ( 2 , "0" ) ;
18+ const offsetMinutes = String ( absoluteOffsetMinutes % 60 ) . padStart ( 2 , "0" ) ;
19+
20+ return `${ year } -${ month } -${ day } ${ hour } :${ minute } UTC${ offsetSign } ${ offsetHours } :${ offsetMinutes } ` ;
21+ }
22+
723function flushPromises ( ) : Promise < void > {
824 return new Promise ( ( resolve ) => {
925 setTimeout ( resolve , 0 ) ;
@@ -41,6 +57,55 @@ describe("MembershipRequestDetailPage", () => {
4157 vi . restoreAllMocks ( ) ;
4258 } ) ;
4359
60+ it ( "renders requested-at with the shared membership timestamp format" , async ( ) => {
61+ const requestedAt = "2026-04-26T10:00:00+00:00" ;
62+ vi . stubGlobal (
63+ "fetch" ,
64+ vi . fn ( ) . mockResolvedValue (
65+ new Response (
66+ JSON . stringify ( {
67+ viewer : {
68+ mode : "committee" ,
69+ } ,
70+ request : {
71+ id : 42 ,
72+ status : "pending" ,
73+ requested_at : requestedAt ,
74+ requested_by : { show : false , username : "" , full_name : "" , deleted : false } ,
75+ requested_for : { show : false , kind : "user" , label : "" , username : "" , organization_id : null , deleted : false } ,
76+ membership_type : { name : "Mirror" } ,
77+ responses : [ ] ,
78+ } ,
79+ committee : {
80+ reopen : { show : false } ,
81+ actions : {
82+ canRequestInfo : true ,
83+ showOnHoldApprove : false ,
84+ } ,
85+ } ,
86+ } ) ,
87+ ) ,
88+ ) ,
89+ ) ;
90+
91+ const wrapper = mount ( MembershipRequestDetailPage , {
92+ props : { bootstrap } ,
93+ global : {
94+ stubs : {
95+ MembershipNotesCard : true ,
96+ MembershipRequestDetailActions : true ,
97+ } ,
98+ } ,
99+ } ) ;
100+
101+ await flushPromises ( ) ;
102+ await flushPromises ( ) ;
103+
104+ expect ( wrapper . text ( ) ) . toContain ( "Requested at" ) ;
105+ expect ( wrapper . text ( ) ) . toContain ( expectedLocalTimestamp ( requestedAt ) ) ;
106+ expect ( wrapper . text ( ) ) . not . toContain ( requestedAt ) ;
107+ } ) ;
108+
44109 it ( "posts to the reopen endpoint with CSRF, refetches detail, and renders warning/deleted markers" , async ( ) => {
45110 const fetchMock = vi
46111 . fn ( )
0 commit comments