diff --git a/package.json b/package.json index e28a853..f3e572b 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@types/supertest": "^2.0.12", "@whatwg-node/fetch": "^0.9.14", "eslint": "^8.55.0", - "hono": "^3.11.7", + "hono": "^3.12.8", "jest": "^29.6.1", "np": "^7.7.0", "prettier": "^3.2.4", diff --git a/src/response.ts b/src/response.ts index 1a4ef07..2c68b8e 100644 --- a/src/response.ts +++ b/src/response.ts @@ -34,7 +34,7 @@ export class Response { } if (typeof body === 'string' || body instanceof ReadableStream) { - let headers = (init?.headers || { 'content-type': 'text/plain;charset=UTF-8' }) as + let headers = (init?.headers || { 'content-type': 'text/plain; charset=UTF-8' }) as | Record | Headers | OutgoingHttpHeaders diff --git a/src/utils.ts b/src/utils.ts index 97cac17..8117fbb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -55,7 +55,7 @@ export const buildOutgoingHttpHeaders = (headers: Headers): OutgoingHttpHeaders if (cookies.length > 0) { res['set-cookie'] = cookies } - res['content-type'] ??= 'text/plain;charset=UTF-8' + res['content-type'] ??= 'text/plain; charset=UTF-8' return res } diff --git a/test/server.test.ts b/test/server.test.ts index 0cd11d3..db48372 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -40,7 +40,7 @@ describe('Basic', () => { it('Should return 200 response - GET /', async () => { const res = await request(server).get('/') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Hello! Node!') }) @@ -53,7 +53,7 @@ describe('Basic', () => { it('Should return 200 response - GET /user-agent', async () => { const res = await request(server).get('/user-agent').set('user-agent', 'Hono') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Hono') }) @@ -72,13 +72,13 @@ describe('Basic', () => { it('Should return 500 response - GET /invalid', async () => { const res = await request(server).get('/invalid') expect(res.status).toBe(500) - expect(res.headers['content-type']).toBe('text/plain') + expect(res.headers['content-type']).toEqual('text/plain') }) it('Should return 200 response - GET /ponyfill', async () => { const res = await request(server).get('/ponyfill') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Pony') }) }) @@ -190,28 +190,28 @@ describe('Response body', () => { it('Should return JSON body', async () => { const res = await request(server).get('/json') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/application\/json/) + expect(res.headers['content-type']).toMatch('application/json') expect(JSON.parse(res.text)).toEqual({ foo: 'bar' }) }) it('Should return JSON body from /json-async', async () => { const res = await request(server).get('/json-async') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/application\/json/) + expect(res.headers['content-type']).toMatch('application/json') expect(JSON.parse(res.text)).toEqual({ foo: 'async' }) }) it('Should return HTML', async () => { const res = await request(server).get('/html') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/html/) + expect(res.headers['content-type']).toMatch('text/html') expect(res.text).toBe('

Hello!

') }) it('Should return HTML from /html-async', async () => { const res = await request(server).get('/html-async') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/html/) + expect(res.headers['content-type']).toMatch('text/html') expect(res.text).toBe('

Hello!

') }) }) @@ -236,14 +236,14 @@ describe('Response body', () => { it('Should return JSON body from /json-blob', async () => { const res = await request(server).get('/json-blob') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/application\/json/) + expect(res.headers['content-type']).toMatch('application/json') expect(JSON.parse(res.text)).toEqual({ foo: 'blob' }) }) it('Should return JSON body from /json-buffer', async () => { const res = await request(server).get('/json-buffer') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/application\/json/) + expect(res.headers['content-type']).toMatch('application/json') expect(JSON.parse(res.text)).toEqual({ foo: 'buffer' }) }) }) @@ -395,7 +395,7 @@ describe('Stream and non-stream response', () => { const res = await request(server).get('/json') expect(res.status).toBe(200) expect(res.headers['content-length']).toMatch('13') - expect(res.headers['content-type']).toMatch(/application\/json/) + expect(res.headers['content-type']).toMatch('application/json') expect(JSON.parse(res.text)).toEqual({ foo: 'bar' }) }) @@ -403,7 +403,7 @@ describe('Stream and non-stream response', () => { const res = await request(server).get('/text') expect(res.status).toBe(200) expect(res.headers['content-length']).toMatch('6') - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Hello!') }) @@ -411,8 +411,8 @@ describe('Stream and non-stream response', () => { const res = await request(server).get('/json-stream') expect(res.status).toBe(200) expect(res.headers['content-length']).toBeUndefined() - expect(res.headers['content-type']).toMatch(/application\/json/) - expect(res.headers['transfer-encoding']).toMatch(/chunked/) + expect(res.headers['content-type']).toMatch('application/json') + expect(res.headers['transfer-encoding']).toMatch('chunked') expect(JSON.parse(res.text)).toEqual({ foo: 'bar' }) }) @@ -430,8 +430,8 @@ describe('Stream and non-stream response', () => { }) expect(res.status).toBe(200) expect(res.headers['content-length']).toBeUndefined() - expect(res.headers['content-type']).toMatch(/text\/event-stream/) - expect(res.headers['transfer-encoding']).toMatch(/chunked/) + expect(res.headers['content-type']).toMatch('text/event-stream') + expect(res.headers['transfer-encoding']).toMatch('chunked') }) it('Should return error - stream without app crashing', async () => { @@ -456,7 +456,7 @@ describe('SSL', () => { it('Should return 200 response - GET /', async () => { const res = await request(server).get('/').trustLocalhost() expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Hello! Node!') }) }) @@ -480,7 +480,7 @@ describe('HTTP2', () => { // @ts-expect-error: @types/supertest is not updated yet const res = await request(server, { http2: true }).get('/').trustLocalhost() expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Hello! Node!') }) @@ -488,7 +488,7 @@ describe('HTTP2', () => { // @ts-expect-error: @types/supertest is not updated yet const res = await request(server, { http2: true }).get('/headers').trustLocalhost() expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(res.text).toBe('Hello! Node!') }) @@ -497,15 +497,27 @@ describe('HTTP2', () => { // @ts-expect-error: @types/supertest is not updated yet const res = await request(server, { http2: true }).get('/url').trustLocalhost() expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) + expect(res.headers['content-type']).toMatch('text/plain') expect(new URL(res.text).hostname).toBe('127.0.0.1') }) }) -describe('Hono compression', () => { +describe('Hono compression default gzip', () => { const app = new Hono() app.use('*', compress()) + app.notFound((c) => { + return c.text('Custom NotFound', 404) + }) + + app.onError((_, c) => { + return c.text('Custom Error!', 500) + }) + + app.get('/error', () => { + throw new Error() + }) + app.get('/one', async (c) => { let body = 'one' @@ -515,12 +527,82 @@ describe('Hono compression', () => { return c.text(body) }) - it('Should return 200 response - GET /one', async () => { + it('should return 200 response - GET /one', async () => { const server = createAdaptorServer(app) const res = await request(server).get('/one') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/text\/plain/) - expect(res.headers['content-encoding']).toMatch(/gzip/) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.headers['content-encoding']).toMatch('gzip') + }) + + it('should return 404 Custom NotFound', async () => { + const server = createAdaptorServer(app) + const res = await request(server).get('/err') + expect(res.status).toBe(404) + expect(res.text).toEqual('Custom NotFound') + expect(res.headers['content-type']).toEqual('text/plain; charset=UTF-8') + expect(res.headers['content-encoding']).toMatch('gzip') + }) + + it('should return 500 Custom Error!', async () => { + const server = createAdaptorServer(app) + const res = await request(server).get('/error') + expect(res.status).toBe(500) + expect(res.text).toEqual('Custom Error!') + expect(res.headers['content-type']).toEqual('text/plain; charset=UTF-8') + expect(res.headers['content-encoding']).toMatch('gzip') + }) +}) + +describe('Hono compression deflate', () => { + const app = new Hono() + app.use('*', compress({ encoding: 'deflate' })) + + app.notFound((c) => { + return c.text('Custom NotFound', 404) + }) + + app.onError((_, c) => { + return c.text('Custom Error!', 500) + }) + + app.get('/error', () => { + throw new Error() + }) + + app.get('/one', async (c) => { + let body = 'one' + + for (let index = 0; index < 1000 * 1000; index++) { + body += ' one' + } + return c.text(body) + }) + + it('should return 200 response - GET /one', async () => { + const server = createAdaptorServer(app) + const res = await request(server).get('/one') + expect(res.status).toBe(200) + expect(res.headers['content-type']).toMatch('text/plain') + expect(res.headers['content-encoding']).toMatch('deflate') + }) + + it('should return 404 Custom NotFound', async () => { + const server = createAdaptorServer(app) + const res = await request(server).get('/err') + expect(res.status).toBe(404) + expect(res.text).toEqual('Custom NotFound') + expect(res.headers['content-type']).toEqual('text/plain; charset=UTF-8') + expect(res.headers['content-encoding']).toMatch('deflate') + }) + + it('should return 500 Custom Error!', async () => { + const server = createAdaptorServer(app) + const res = await request(server).get('/error') + expect(res.status).toBe(500) + expect(res.text).toEqual('Custom Error!') + expect(res.headers['content-type']).toEqual('text/plain; charset=UTF-8') + expect(res.headers['content-encoding']).toMatch('deflate') }) }) @@ -540,7 +622,7 @@ describe('set child response to c.res', () => { const server = createAdaptorServer(app) const res = await request(server).get('/json') expect(res.status).toBe(200) - expect(res.headers['content-type']).toMatch(/application\/json/) + expect(res.headers['content-type']).toMatch('application/json') }) }) diff --git a/yarn.lock b/yarn.lock index 6b5b8eb..1e2609a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2924,10 +2924,10 @@ hexoid@^1.0.0: resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== -hono@^3.11.7: - version "3.11.7" - resolved "https://registry.yarnpkg.com/hono/-/hono-3.11.7.tgz#e44f8aa3a18f19775304328bde6f559f7a02447f" - integrity sha512-TcfAq7IdipF+9coxnuzYlSSBXbm9mTyWjjagLCv/2ampboNcKJdi+XCK5G48mHQtpI5+9Rj3J4FfcGgw9vzIww== +hono@^3.12.8: + version "3.12.8" + resolved "https://registry.yarnpkg.com/hono/-/hono-3.12.8.tgz#7c137aa6ac7bcd2aec3f55b9596d71e97081963a" + integrity sha512-vnOEIRdqsp4uHE/dkOBr9EYmTsR86sD/FyG2xhfAQzR9udDRglN1nuO7SGc/7U3HfSorc6PSCNGN6upnVtCmfg== hosted-git-info@^2.1.4: version "2.8.9"