describe('Sleep Buddy (睡眠打卡)', () => { beforeEach(() => { cy.visit('/sleep-buddy', { onBeforeLoad(win) { win.localStorage.setItem('sp_login_expires', String(Date.now() + 86400000)) } }) }) it('shows login form when not logged in as buddy', () => { cy.get('.buddy-login').should('be.visible') cy.get('.buddy-login-logo').should('contain', '🌙') cy.contains('睡眠打卡').should('be.visible') cy.contains('和好友一起早睡').should('be.visible') }) it('has username and password fields', () => { cy.get('.buddy-login-card input').should('have.length.gte', 2) cy.get('.buddy-login-card input[type="password"]').should('exist') }) it('toggle between login and register mode', () => { cy.get('.buddy-toggle-btn').should('contain', '没有账号?注册') cy.get('.buddy-toggle-btn').click() cy.get('.buddy-main-btn').should('contain', '注册') cy.get('.buddy-login-card input').should('have.length', 3) // username, password, confirm cy.get('.buddy-toggle-btn').should('contain', '已有账号?登录') }) it('register mode shows confirm password', () => { cy.get('.buddy-toggle-btn').click() cy.get('.buddy-login-card input[type="password"]').should('have.length', 2) }) it('shows error for mismatched passwords during register', () => { cy.get('.buddy-toggle-btn').click() cy.get('.buddy-login-card input').eq(0).type('testuser') cy.get('.buddy-login-card input').eq(1).type('pass1') cy.get('.buddy-login-card input').eq(2).type('pass2') cy.get('.buddy-main-btn').click() cy.get('.buddy-error').should('contain', '密码不一致') }) it('register then login flow', () => { const user = 'testuser_' + Date.now() // Register cy.get('.buddy-toggle-btn').click() cy.get('.buddy-login-card input').eq(0).type(user) cy.get('.buddy-login-card input').eq(1).type('testpass') cy.get('.buddy-login-card input').eq(2).type('testpass') cy.get('.buddy-main-btn').click() // Should be logged in cy.get('.buddy-main', { timeout: 5000 }).should('be.visible') cy.contains(user).should('be.visible') }) // Tests that require buddy login describe('when logged in', () => { const user = 'cy_test_' + Math.random().toString(36).slice(2, 8) beforeEach(() => { cy.visit('/sleep-buddy', { onBeforeLoad(win) { win.localStorage.setItem('sp_login_expires', String(Date.now() + 86400000)) win.localStorage.setItem('buddy_session', JSON.stringify({ username: user, exp: Date.now() + 86400000 })) } }) // Register the user via API first cy.window().then(async (win) => { const buf = await win.crypto.subtle.digest('SHA-256', new TextEncoder().encode('testpass')) const hash = Array.from(new Uint8Array(buf)).map(b => b.toString(16).padStart(2, '0')).join('') try { await fetch('/api/buddy-register', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: user, hash }) }) } catch {} }) }) it('shows main buddy interface', () => { cy.get('.buddy-main', { timeout: 5000 }).should('be.visible') cy.get('.sleep-btn').should('contain', '我去睡觉啦') }) it('shows target time card', () => { cy.get('.target-card').should('be.visible') cy.get('.target-time').should('not.be.empty') }) it('shows record input', () => { cy.get('.record-card').should('be.visible') cy.get('.capture-row input').should('be.visible') }) it('records sleep time', () => { cy.get('.capture-row input').type('22:30') cy.get('.capture-row button').click() cy.get('.buddy-hint').should('contain', '已记录') }) it('shows error for unrecognized input', () => { cy.get('.capture-row input').type('乱七八糟') cy.get('.capture-row button').click() cy.get('.buddy-hint').should('contain', '无法识别') }) it('go sleep button sends notification', () => { cy.get('.sleep-btn').click() cy.get('.buddy-hint').should('contain', '晚安') }) it('user menu shows logout', () => { cy.get('.user-chip').click() cy.get('.user-menu button').should('contain', '退出登录') }) it('logout returns to login form', () => { cy.get('.user-chip').click() cy.contains('退出登录').click() cy.get('.buddy-login').should('be.visible') }) }) })