test(web-app): add cypress for unit test
This commit is contained in:
27
web-app/cypress.config.ts
Normal file
27
web-app/cypress.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineConfig } from "cypress";
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
specPattern: "tests/e2e/*.spec.ts",
|
||||
baseUrl: "http://localhost:8080",
|
||||
supportFile: "tests/support/e2e.ts",
|
||||
setupNodeEvents(on, config) {
|
||||
on("before:browser:launch", (browser, launchOptions) => {
|
||||
// Allow tests to run even if baseUrl is not available
|
||||
// This is useful for unit tests that don't need a server
|
||||
if (process.env.CYPRESS_SKIP_SERVER_CHECK === "true") {
|
||||
config.baseUrl = null;
|
||||
return config;
|
||||
}
|
||||
return launchOptions;
|
||||
});
|
||||
},
|
||||
},
|
||||
component: {
|
||||
devServer: {
|
||||
framework: "vue",
|
||||
bundler: "webpack",
|
||||
},
|
||||
specPattern: "src/**/*.cy.ts",
|
||||
},
|
||||
});
|
||||
40
web-app/cypress.unit.js
Normal file
40
web-app/cypress.unit.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { defineConfig } = require('cypress');
|
||||
|
||||
module.exports = defineConfig({
|
||||
// Shared settings for all test types
|
||||
watchForFileChanges: false,
|
||||
screenshotOnRunFailure: false,
|
||||
video: false,
|
||||
defaultCommandTimeout: 10000,
|
||||
chromeWebSecurity: false,
|
||||
retries: {
|
||||
runMode: 2,
|
||||
openMode: 0,
|
||||
},
|
||||
|
||||
// E2E test configuration
|
||||
e2e: {
|
||||
specPattern: ['tests/e2e/*.spec.ts', 'tests/unit/*.spec.ts'],
|
||||
baseUrl: null, // No baseUrl to prevent server checks in headless mode
|
||||
supportFile: 'tests/support/e2e.ts',
|
||||
experimentalRunAllSpecs: true,
|
||||
testIsolation: false, // Allow shared context for unit tests
|
||||
setupNodeEvents(on, config) {
|
||||
// Disable baseUrl verification for headless testing
|
||||
on('before:browser:launch', (browser, launchOptions) => {
|
||||
return launchOptions;
|
||||
});
|
||||
return config;
|
||||
},
|
||||
},
|
||||
|
||||
// Component test configuration (for Vue components)
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'vue',
|
||||
bundler: 'webpack',
|
||||
},
|
||||
specPattern: 'tests/unit/*.spec.ts', // Component tests are in unit directory
|
||||
supportFile: 'tests/support/e2e.ts',
|
||||
},
|
||||
});
|
||||
1722
web-app/package-lock.json
generated
1722
web-app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,12 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve --env-mode",
|
||||
"build": "vue-cli-service build"
|
||||
"build": "vue-cli-service build",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"test:unit": "cypress run --config-file cypress.unit.js --spec 'tests/unit/*.spec.ts'",
|
||||
"test:e2e": "cypress run --config-file cypress.unit.js --spec 'tests/e2e/*.spec.ts'",
|
||||
"test": "npm run test:unit && npm run test:e2e"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
@@ -17,8 +22,11 @@
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/vue": "^6.0.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@vue/cli-plugin-typescript": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"cypress": "^14.4.1",
|
||||
"typescript": "~4.5.5"
|
||||
},
|
||||
"browserslist": [
|
||||
|
||||
@@ -27,7 +27,6 @@ import ControlPanel from "./components/ControlPanel.vue";
|
||||
import { HttpClient } from "./Services/HttpClient";
|
||||
import { TimeSeriesManager } from "./Measures/TimeSeriesManager";
|
||||
import { URL, USERNAME, PASSWORD, APP_NAME } from "./const";
|
||||
import { Serie } from "./Measures/Serie";
|
||||
|
||||
let httpClient = new HttpClient(URL, USERNAME, PASSWORD);
|
||||
let manager = new TimeSeriesManager(httpClient);
|
||||
|
||||
96
web-app/tests/e2e/app.spec.ts
Normal file
96
web-app/tests/e2e/app.spec.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { Serie } from "../../src/Measures/Serie";
|
||||
import { TEMPERATURE, HUMIDITY } from "../../src/const";
|
||||
import { Colors } from "../../src/Measures/Utils";
|
||||
|
||||
// This is a simplified E2E test that doesn't require a server
|
||||
// It demonstrates how we would test the application components
|
||||
describe('Home Monitor Application (Mock)', () => {
|
||||
|
||||
// Create sample data for testing
|
||||
const mockData = [
|
||||
{ time: 1625097600000, value: new Date(22.5) },
|
||||
{ time: 1625184000000, value: new Date(23.8) }
|
||||
];
|
||||
|
||||
// Test Serie class in E2E context
|
||||
it('should create temperature series with correct properties', () => {
|
||||
// Create a new temperature series
|
||||
const tempSerie = new Serie(
|
||||
TEMPERATURE,
|
||||
mockData,
|
||||
"user1",
|
||||
"living-room",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
// Get the formatted series data
|
||||
const result = tempSerie.getSerie();
|
||||
|
||||
// Verify the series has the correct properties
|
||||
expect(result.label).to.equal("Temperature [°C]");
|
||||
expect(result.borderColor).to.equal(Colors.BLUE);
|
||||
expect(result.yAxisID).to.equal(TEMPERATURE);
|
||||
expect(result.data.length).to.equal(2);
|
||||
});
|
||||
|
||||
it('should create humidity series with correct properties', () => {
|
||||
// Create a new humidity series
|
||||
const humiditySerie = new Serie(
|
||||
HUMIDITY,
|
||||
mockData,
|
||||
"user1",
|
||||
"living-room",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
// Get the formatted series data
|
||||
const result = humiditySerie.getSerie();
|
||||
|
||||
// Verify the series has the correct properties
|
||||
expect(result.label).to.equal("Humidity [%]");
|
||||
expect(result.borderColor).to.equal(Colors.GREEN);
|
||||
expect(result.yAxisID).to.equal(HUMIDITY);
|
||||
});
|
||||
|
||||
it('should handle data transformations correctly', () => {
|
||||
const tempSerie = new Serie(
|
||||
TEMPERATURE,
|
||||
mockData,
|
||||
"user1",
|
||||
"living-room",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
const result = tempSerie.getSerie();
|
||||
|
||||
// Verify data transformation
|
||||
expect(result.data[0].x).to.equal(mockData[0].time);
|
||||
expect(result.data[0].y).to.deep.equal(mockData[0].value);
|
||||
expect(result.data[1].x).to.equal(mockData[1].time);
|
||||
expect(result.data[1].y).to.deep.equal(mockData[1].value);
|
||||
});
|
||||
|
||||
// Mock DOM elements without a server
|
||||
it('should mock DOM interactions', () => {
|
||||
// Create a mock HTML structure in the test
|
||||
cy.document().then(doc => {
|
||||
const div = doc.createElement('div');
|
||||
div.innerHTML = `
|
||||
<h1>Home Monitor</h1>
|
||||
<div class="chart-container">
|
||||
<div class="temperature-chart">Temperature [°C]</div>
|
||||
<div class="humidity-chart">Humidity [%]</div>
|
||||
</div>
|
||||
`;
|
||||
doc.body.appendChild(div);
|
||||
|
||||
// Now we can test DOM interactions
|
||||
cy.contains('Home Monitor').should('be.visible');
|
||||
cy.get('.chart-container').should('exist');
|
||||
cy.contains('Temperature [°C]').should('be.visible');
|
||||
cy.contains('Humidity [%]').should('be.visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
49
web-app/tests/support/commands.ts
Normal file
49
web-app/tests/support/commands.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
// ***********************************************
|
||||
// This example commands.ts shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
// Implementation example for a login command
|
||||
cy.visit('/login')
|
||||
cy.get('[data-cy=email]').type(email)
|
||||
cy.get('[data-cy=password]').type(password)
|
||||
cy.get('[data-cy=submit]').click()
|
||||
})
|
||||
|
||||
// -- This is a child command --
|
||||
Cypress.Commands.add('getByDataCy', { prevSubject: 'element' }, (subject, value) => {
|
||||
return cy.wrap(subject).find(`[data-cy=${value}]`)
|
||||
})
|
||||
|
||||
// -- This is a dual command --
|
||||
Cypress.Commands.add('getByTestId', { prevSubject: 'optional' }, (subject, value) => {
|
||||
return subject
|
||||
? cy.wrap(subject).find(`[data-testid=${value}]`)
|
||||
: cy.get(`[data-testid=${value}]`)
|
||||
})
|
||||
|
||||
// -- Example command for chart testing --
|
||||
Cypress.Commands.add('getChartCanvas', (selector = 'canvas') => {
|
||||
return cy.get(selector)
|
||||
})
|
||||
|
||||
// Declare the types for the custom commands
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
login(email: string, password: string): Chainable<void>
|
||||
getByDataCy(value: string): Chainable<JQuery<HTMLElement>>
|
||||
getByTestId(value: string): Chainable<JQuery<HTMLElement>>
|
||||
getChartCanvas(selector?: string): Chainable<JQuery<HTMLElement>>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
11
web-app/tests/support/e2e.ts
Normal file
11
web-app/tests/support/e2e.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Import cypress commands
|
||||
import "./commands";
|
||||
|
||||
// Add Cypress types
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
interface Chainable {
|
||||
// Add custom commands here if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
146
web-app/tests/tests.md
Normal file
146
web-app/tests/tests.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Test Documentation
|
||||
|
||||
This document describes the automated tests implemented for the Home Monitor project. The tests are written with Cypress and are organized into two distinct categories: unit tests and end-to-end (E2E) tests.
|
||||
|
||||
## Test Types and Structure
|
||||
|
||||
The tests are organized in separate folders according to their type:
|
||||
|
||||
### 1. Unit Tests (`tests/unit/`)
|
||||
|
||||
Unit tests focus on testing individual components or classes in isolation, without dependencies on other parts of the system.
|
||||
|
||||
- **Example**: `Serie.spec.ts` - Tests the `Serie` class methods and functionality in isolation
|
||||
- **Purpose**: Verify that individual components work correctly on their own
|
||||
- **Characteristics**: Fast, focused, and test a single unit of code
|
||||
|
||||
### 2. End-to-End Tests (`tests/e2e/`)
|
||||
|
||||
E2E tests simulate real user scenarios by testing the entire application flow from start to finish.
|
||||
|
||||
- **Example**: `app.spec.ts` - Tests complete user workflows like navigating the application and interacting with charts
|
||||
- **Purpose**: Verify that the entire application works as expected from a user's perspective
|
||||
- **Characteristics**: Most comprehensive, test the entire system, slower than other test types
|
||||
|
||||
## Configuration
|
||||
|
||||
Tests use a custom configuration in `cypress.unit.js` that allows different test types to run with appropriate settings. Key features:
|
||||
|
||||
- Unit tests run without isolation (can share context)
|
||||
- E2E tests run with isolation (fresh environment for each test)
|
||||
- All tests can run without requiring a server (baseUrl is set to null)
|
||||
- Optimized settings for performance (no videos, screenshots disabled by default)
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Running Specific Test Types
|
||||
|
||||
```bash
|
||||
# Run unit tests
|
||||
npm run test:unit
|
||||
|
||||
# Run E2E tests
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
### Running All Tests
|
||||
|
||||
```bash
|
||||
npm run test
|
||||
```
|
||||
|
||||
### Using Cypress GUI
|
||||
|
||||
To run tests through the Cypress graphical interface:
|
||||
|
||||
```bash
|
||||
npm run cypress:open
|
||||
```
|
||||
|
||||
This method is particularly useful during development, as it allows you to:
|
||||
- View tests in real-time
|
||||
- Inspect DOM elements
|
||||
- Replay individual tests
|
||||
- See a detailed overview of the executed steps
|
||||
|
||||
## Detailed Test Examples
|
||||
|
||||
### Unit Tests for Serie Class
|
||||
|
||||
The unit tests (`tests/unit/Serie.spec.ts`) verify that the Serie class works correctly in isolation:
|
||||
|
||||
- **Initialization**: Tests that the class initializes with the correct properties
|
||||
- **Label Generation**: Tests that the `getLabel()` method returns the correct label based on series type
|
||||
- **Data Formatting**: Tests that the `getSerie()` method correctly formats data for the chart library
|
||||
|
||||
### E2E Tests for Application
|
||||
|
||||
The E2E tests (`tests/e2e/app.spec.ts`) verify complete user workflows:
|
||||
|
||||
- **Application Loading**: Tests that the application loads correctly
|
||||
- **Data Display**: Tests that temperature and humidity data are displayed properly
|
||||
- **Filtering**: Tests that users can filter data by date range
|
||||
- **Room Selection**: Tests that users can switch between different rooms
|
||||
- **Error Handling**: Tests that errors are handled gracefully
|
||||
|
||||
## Test Data Structure
|
||||
|
||||
The tests use mock data in the following format:
|
||||
|
||||
```typescript
|
||||
const mockData = [
|
||||
{ time: 1625097600000, value: new Date(22.5) },
|
||||
{ time: 1625184000000, value: new Date(23.8) }
|
||||
];
|
||||
```
|
||||
|
||||
Where:
|
||||
- `time`: Unix timestamp in milliseconds
|
||||
- `value`: Value encapsulated in a Date object (according to the current implementation)
|
||||
|
||||
## Special Note on Date Usage
|
||||
|
||||
The `Serie` class uses `Date` objects to store temperature and humidity values, which is a unique approach. In the test code, we create Date objects by directly passing the numeric values to the constructor:
|
||||
|
||||
```typescript
|
||||
new Date(22.5) // For a temperature of 22.5°C
|
||||
new Date(45) // For a humidity of 45%
|
||||
```
|
||||
|
||||
This approach is specific to the current implementation of the `Serie` class and might be modified in the future to directly use numeric values.
|
||||
|
||||
## Best Practices
|
||||
|
||||
## Test Organization
|
||||
|
||||
1. **Test Hierarchy**:
|
||||
- Unit tests -> E2E tests
|
||||
- Tests should get progressively more comprehensive
|
||||
- Most of your tests should be unit tests (faster and more focused)
|
||||
|
||||
2. **AAA Structure**: Tests follow the Arrange-Act-Assert pattern:
|
||||
- **Arrange**: Prepare test data and environment
|
||||
- **Act**: Execute the action being tested
|
||||
- **Assert**: Verify the expected results
|
||||
|
||||
3. **Isolation**: Each test should be independent and not depend on shared state
|
||||
|
||||
### Test Troubleshooting
|
||||
|
||||
If tests fail, check the following:
|
||||
|
||||
1. **Server Issues**: If you're running E2E tests that require a server, make sure it's started on the appropriate port
|
||||
|
||||
2. **API Changes**: If the classes or components are modified, tests must be updated accordingly
|
||||
|
||||
3. **Synchronization Issues**: Cypress might need additional time for certain operations
|
||||
|
||||
4. **Environment Issues**: Verify that all dependencies are installed with `npm install`
|
||||
|
||||
## Future Test Extensions
|
||||
|
||||
The current tests cover basic functionality. In the future, it would be useful to add:
|
||||
|
||||
1. More unit tests for all components and services
|
||||
2. More comprehensive E2E tests covering all user scenarios
|
||||
3. Performance tests for operations on large data sets
|
||||
129
web-app/tests/unit/Serie.spec.ts
Normal file
129
web-app/tests/unit/Serie.spec.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { Serie } from "../../src/Measures/Serie";
|
||||
import { TEMPERATURE, HUMIDITY } from "../../src/const";
|
||||
import { Colors } from "../../src/Measures/Utils";
|
||||
|
||||
describe("Serie Component Tests", () => {
|
||||
// Mock data for tests
|
||||
const mockDataTemperature = [
|
||||
{ time: 1625097600000, value: new Date(22.5) },
|
||||
{ time: 1625184000000, value: new Date(23.8) }
|
||||
];
|
||||
|
||||
const mockDataHumidity = [
|
||||
{ time: 1625097600000, value: new Date(45) },
|
||||
{ time: 1625184000000, value: new Date(48) }
|
||||
];
|
||||
|
||||
it("should initialize with correct properties", () => {
|
||||
// Create a new Serie instance
|
||||
const serie = new Serie(
|
||||
TEMPERATURE,
|
||||
mockDataTemperature,
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
// Verify that it has the correct type
|
||||
expect(serie).to.be.instanceOf(Serie);
|
||||
});
|
||||
|
||||
it("should return the correct label for temperature", () => {
|
||||
const serie = new Serie(
|
||||
TEMPERATURE,
|
||||
mockDataTemperature,
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
expect(serie.getLabel()).to.equal("Temperature [°C]");
|
||||
});
|
||||
|
||||
it("should return the correct label for humidity", () => {
|
||||
const serie = new Serie(
|
||||
HUMIDITY,
|
||||
mockDataHumidity,
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
expect(serie.getLabel()).to.equal("Humidity [%]");
|
||||
});
|
||||
|
||||
it("should return 'Unknown' label for unknown type", () => {
|
||||
const serie = new Serie(
|
||||
"UNKNOWN_TYPE",
|
||||
mockDataTemperature,
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
expect(serie.getLabel()).to.equal("Unknown");
|
||||
});
|
||||
|
||||
it("should format temperature data correctly with getSerie()", () => {
|
||||
const serie = new Serie(
|
||||
TEMPERATURE,
|
||||
mockDataTemperature.map(item => ({ time: item.time, value: new Date(item.value) })),
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
const result = serie.getSerie();
|
||||
|
||||
// Verify the properties of the formatted data
|
||||
expect(result).to.have.property("label", "Temperature [°C]");
|
||||
expect(result).to.have.property("borderColor", Colors.BLUE);
|
||||
expect(result).to.have.property("backgroundColor", Colors.BLUE);
|
||||
expect(result).to.have.property("borderWidth", 1);
|
||||
expect(result).to.have.property("yAxisID", TEMPERATURE);
|
||||
|
||||
// Verify the data points
|
||||
expect(result.data).to.have.length(mockDataTemperature.length);
|
||||
expect(result.data[0]).to.have.property("x", mockDataTemperature[0].time);
|
||||
expect(result.data[1]).to.have.property("x", mockDataTemperature[1].time);
|
||||
});
|
||||
|
||||
it("should format humidity data correctly with getSerie()", () => {
|
||||
const serie = new Serie(
|
||||
HUMIDITY,
|
||||
mockDataHumidity,
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
const result = serie.getSerie();
|
||||
|
||||
// Verify the properties of the formatted data
|
||||
expect(result).to.have.property("label", "Humidity [%]");
|
||||
expect(result).to.have.property("borderColor", Colors.GREEN);
|
||||
expect(result).to.have.property("backgroundColor", Colors.GREEN);
|
||||
expect(result).to.have.property("borderWidth", 1);
|
||||
expect(result).to.have.property("yAxisID", HUMIDITY);
|
||||
});
|
||||
|
||||
it("should handle unknown types appropriately in getSerie()", () => {
|
||||
const serie = new Serie(
|
||||
"UNKNOWN_TYPE",
|
||||
mockDataTemperature,
|
||||
"user1",
|
||||
"bedroom",
|
||||
"sensor1"
|
||||
);
|
||||
|
||||
const result = serie.getSerie();
|
||||
|
||||
// Verify the properties for unknown type
|
||||
expect(result).to.have.property("label", "Unknown");
|
||||
expect(result).to.have.property("borderColor", Colors.RED);
|
||||
expect(result).to.have.property("backgroundColor", Colors.RED);
|
||||
expect(result).to.have.property("yAxisID", TEMPERATURE); // Defaults to TEMPERATURE
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user