11const request = require ( 'supertest' ) ;
22const app = require ( '../server' ) ;
3+ const { JSDOM } = require ( 'jsdom' ) ;
4+ const fs = require ( 'fs' ) ;
5+ const path = require ( 'path' ) ;
36
47describe ( 'Weather API' , ( ) => {
58 it ( 'returns weather data for a valid city' , async ( ) => {
@@ -8,10 +11,31 @@ describe('Weather API', () => {
811
912 expect ( response . statusCode ) . toBe ( 200 ) ;
1013 expect ( response . body ) . toHaveProperty ( 'name' , 'London' ) ;
14+ // Branch coverage for description line 31: test with description present
1115 expect ( response . body ) . toHaveProperty ( 'description' ) ;
16+ expect ( typeof response . body . description ) . toBe ( 'string' ) ;
17+ expect ( response . body . description . length ) . toBeGreaterThan ( 0 ) ;
1218 expect ( response . body ) . toHaveProperty ( 'temp' ) ;
1319 } ) ;
1420
21+ it ( 'returns weather data with empty description' , async ( ) => {
22+ // Mock fetch to return data with no description
23+ jest . spyOn ( global , 'fetch' ) . mockResolvedValueOnce ( {
24+ ok : true ,
25+ json : jest . fn ( ) . mockResolvedValue ( {
26+ name : 'TestCity' ,
27+ weather : [ { } ] ,
28+ main : { temp : 70 , feels_like : 65 } ,
29+ wind : { speed : 5 }
30+ } ) ,
31+ } ) ;
32+
33+ const response = await request ( app ) . get ( '/api/weather?city=TestCity' ) ;
34+ expect ( response . statusCode ) . toBe ( 200 ) ;
35+ expect ( response . body ) . toHaveProperty ( 'description' , '' ) ;
36+ global . fetch . mockRestore ( ) ;
37+ } ) ;
38+
1539 it ( 'returns 400 if city is missing' , async ( ) => {
1640 const response = await request ( app ) . get ( '/api/weather' ) ;
1741 expect ( response . statusCode ) . toBe ( 400 ) ;
@@ -20,36 +44,79 @@ describe('Weather API', () => {
2044} ) ;
2145
2246describe ( 'Weather API error handling' , ( ) => {
23- // Before each test, spy on the global fetch (Node 18+) or require('node-fetch') if using that
2447 beforeEach ( ( ) => {
25- jest . spyOn ( global , 'fetch' ) ; // if on Node 18+, 'global.fetch' is the built-in
48+ jest . spyOn ( global , 'fetch' ) ;
2649 } ) ;
2750
2851 afterEach ( ( ) => {
29- global . fetch . mockRestore ( ) ; // restore original fetch
52+ global . fetch . mockRestore ( ) ;
3053 } ) ;
3154
3255 it ( 'handles non-OK response from OpenWeather' , async ( ) => {
33- // Force fetch to return { ok: false, status: 404, ... }
3456 global . fetch . mockResolvedValue ( {
3557 ok : false ,
3658 status : 404 ,
3759 json : jest . fn ( ) . mockResolvedValue ( { } ) ,
3860 } ) ;
3961
4062 const response = await request ( app ) . get ( '/api/weather?city=FakeCity' ) ;
41- // The code sets status to response.status => 404 in this case
4263 expect ( response . statusCode ) . toBe ( 404 ) ;
4364 expect ( response . body ) . toHaveProperty ( 'error' , 'Failed to fetch weather data' ) ;
4465 } ) ;
4566
4667 it ( 'handles fetch throw (network error, etc.)' , async ( ) => {
47- // Force fetch to throw an error
4868 global . fetch . mockRejectedValue ( new Error ( 'Network error' ) ) ;
4969
5070 const response = await request ( app ) . get ( '/api/weather?city=FakeCity2' ) ;
51- // The code should catch and return status 500, { error: 'Server error' }
5271 expect ( response . statusCode ) . toBe ( 500 ) ;
5372 expect ( response . body ) . toHaveProperty ( 'error' , 'Server error' ) ;
5473 } ) ;
5574} ) ;
75+
76+ describe ( 'Frontend script' , ( ) => {
77+ let window ;
78+ let document ;
79+
80+ beforeEach ( ( ) => {
81+ const dom = new JSDOM ( `
82+ <select id="citySelect">
83+ <option value="">Select a city...</option>
84+ </select>
85+ <button id="getWeatherBtn">Get Weather</button>
86+ <p id="errorMessage" class="error"></p>
87+ ` , { runScripts : "dangerously" , resources : "usable" } ) ;
88+
89+ window = dom . window ;
90+ document = window . document ;
91+
92+ global . document = document ;
93+ global . window = window ;
94+
95+ global . fetch = jest . fn ( ( ) =>
96+ Promise . resolve ( {
97+ ok : true ,
98+ json : ( ) => Promise . resolve ( [ 'City1' , 'City2' ] ) ,
99+ } )
100+ ) ;
101+
102+ const scriptContent = fs . readFileSync ( path . resolve ( __dirname , '../public/script.js' ) , 'utf-8' ) ;
103+ const scriptEl = document . createElement ( 'script' ) ;
104+ scriptEl . textContent = scriptContent ;
105+ document . body . appendChild ( scriptEl ) ;
106+ } ) ;
107+
108+ afterEach ( ( ) => {
109+ delete global . document ;
110+ delete global . window ;
111+ delete global . fetch ;
112+ } ) ;
113+
114+ it ( 'shows error if no city selected' , ( ) => {
115+ const getWeatherBtn = document . getElementById ( 'getWeatherBtn' ) ;
116+ const errorMessage = document . getElementById ( 'errorMessage' ) ;
117+
118+ getWeatherBtn . click ( ) ;
119+
120+ expect ( errorMessage . textContent ) . toBe ( 'Please select a city!' ) ;
121+ } ) ;
122+ } ) ;
0 commit comments