diff --git a/src/core/application.ts b/src/core/application.ts index 64cf851..5684f38 100644 --- a/src/core/application.ts +++ b/src/core/application.ts @@ -2,9 +2,12 @@ import { IApplication, IRouteMetadata, TRouterMethod } from './types'; import Koa from 'koa'; import Router from '@koa/router'; import { getRoutes } from './route'; +import { viewMiddleware } from './view-middleware'; export function create(): IApplication { const app = new Koa(); + app.use(viewMiddleware); + const router = new Router(); getRoutes().forEach((route: IRouteMetadata) => { diff --git a/src/core/types.ts b/src/core/types.ts index b5db134..fae16b2 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,11 +1,22 @@ import { Server } from 'http'; import { Middleware } from '@koa/router'; +import { Response } from 'koa'; export interface IApplication { listen(port?: number): Server; } +export interface View { + status?: Response['status']; + headers?: Response['headers']; + body?: Response['body']; + socket?: Response['socket']; + redirect?: Response['redirect']; + attachment?: Response['attachment']; +} + export interface Handler extends Middleware { + (): Promise | View; } export type THttpMethod = diff --git a/src/core/view-middleware.ts b/src/core/view-middleware.ts new file mode 100644 index 0000000..a3f74d1 --- /dev/null +++ b/src/core/view-middleware.ts @@ -0,0 +1,31 @@ +import { Context, Next } from 'koa'; + +export async function viewMiddleware(ctx: Context, next: Next) { + const view = await next(); + + if (view) { + if (view.status) { + ctx.response.status = view.status; + } + + if (view.headers) { + ctx.response.headers = view.headers; + } + + if (view.body) { + ctx.response.body = view.body; + } + + if (view.socket) { + ctx.response.socket = view.socket; + } + + if (view.redirect) { + ctx.response.redirect(view.redirect); + } + + if (view.attachment) { + ctx.response.attachment(view.attachment); + } + } +} diff --git a/tests/core/view-middleware.test.ts b/tests/core/view-middleware.test.ts new file mode 100644 index 0000000..85fc186 --- /dev/null +++ b/tests/core/view-middleware.test.ts @@ -0,0 +1,63 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { Context, Next } from 'koa'; +import { viewMiddleware } from '../../src/core/view-middleware'; + +describe('View Middleware', () => { + let ctx: Context; + + beforeEach(() => { + ctx = { + response: {}, + } as Context; + }); + + test('it should set response status', async () => { + const next: Next = vi.fn().mockResolvedValue({ status: 200 }); + + await viewMiddleware(ctx, next); + + expect(ctx.response.status).toBe(200); + }); + + test('it should set response headers', async () => { + const next: Next = vi.fn().mockResolvedValue({ headers: { 'Content-Type': 'application/json' } }); + + await viewMiddleware(ctx, next); + + expect(ctx.response.headers).toEqual({ 'Content-Type': 'application/json' }); + }); + + test('it should set response body', async () => { + const next: Next = vi.fn().mockResolvedValue({ body: 'Hello, World!' }); + + await viewMiddleware(ctx, next); + + expect(ctx.response.body).toBe('Hello, World!'); + }); + + test('it should set response socket', async () => { + const next: Next = vi.fn().mockResolvedValue({ socket: 'socket' }); + + await viewMiddleware(ctx, next); + + expect(ctx.response.socket).toBe('socket'); + }); + + test('it should redirect if view has redirect', async () => { + const next: Next = vi.fn().mockResolvedValue({ redirect: '/home' }); + ctx.response.redirect = vi.fn(); + + await viewMiddleware(ctx, next); + + expect(ctx.response.redirect).toHaveBeenCalledWith('/home'); + }); + + test('it should attach if view has attachment', async () => { + const next: Next = vi.fn().mockResolvedValue({ attachment: 'file.txt' }); + ctx.response.attachment = vi.fn(); + + await viewMiddleware(ctx, next); + + expect(ctx.response.attachment).toHaveBeenCalledWith('file.txt'); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts index af1e632..05c520a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -6,6 +6,8 @@ export default defineConfig({ exclude: [ ...configDefaults.exclude, '**/playground/**', + '**/tests/**', + '**/types.ts', ], }, },