AngularJS Unit Testing Skill Overview This skill specializes in writing, refactoring, and maintaining high-quality unit tests for AngularJS (1.x) applications. It covers controllers, services, filters, directives, HTTP mocking, promises, and dependency injection — everything you need to keep an AngularJS codebase well-tested and reliable. Note : AngularJS reached end-of-life in December 2021. It receives only critical security fixes. New projects should use Angular 19+. For teams maintaining AngularJS codebases, this skill provides the latest testing patterns, tooling, and migration guidance.…

: 'babel-jest'\n },\n collectCoverage: true,\n collectCoverageFrom: ['src/**/*.js', '!src/**/*.spec.js']\n};\n```\n\n**Use when**:\n- You want faster feedback from parallel execution\n- You need better mocking, snapshots, and coverage out of the box\n- You are preparing an AngularJS codebase for an Angular migration\n\n### Karma (Legacy Runner)\n\nKarma is the legacy browser test runner traditionally paired with Jasmine. It is still usable for existing suites, but it has seen no major releases since 2021 and should not be the basis for new investment.\n\n**Configuration**:\n- `karma.conf.js`: Main configuration file\n- Specifies browser environment, files to load, and plugins\n- Supports code coverage reporting and CI integration\n\n## Jest Migration Guide\n\nUse these steps when moving an AngularJS test suite from Jasmine/Karma to Jest:\n\n1. **Install the core tooling**\n ```bash\n npm install --save-dev jest jest-preset-angular angular-mocks\n npm install @angular/core\n ```\n Add `@angular/core` when the repo is hybrid or actively migrating toward Angular.\n\n2. **Create a Jest setup file**\n - Add `setup-jest.js` or `setup-jest.ts`\n - Load `angular`, `angular-mocks`, and any shared test polyfills there\n\n3. **Configure Jest for AngularJS files**\n - Use `jest.config.js` with `testEnvironment: 'jsdom'`\n - Add a transform for legacy JavaScript sources\n - Keep template or DOM-specific setup in the Jest bootstrap file\n\n4. **Load AngularJS modules in Jest**\n ```javascript\n beforeEach(() => {\n require('angular');\n require('angular-mocks');\n angular.mock.module('myApp');\n });\n ```\n\n5. **Migrate spies and stubs**\n - `spyOn(obj, 'method')` → `jest.spyOn(obj, 'method')`\n - `jasmine.createSpy()` → `jest.fn()`\n - `jasmine.createSpyObj()` → `jest.fn()` or explicit mock objects\n\n6. **Replace `$httpBackend` where practical**\n - Prefer `fetch` mocks or MSW for new Jest tests\n - Keep `$httpBackend` only for legacy tests that are expensive to rewrite immediately\n\n7. **Reset state between tests**\n - Use `jest.clearAllMocks()` / `jest.resetAllMocks()`\n - Recreate AngularJS modules and services in `beforeEach()`\n\n## Handling Environmental Flakiness\n\nLegacy AngularJS suites often fail because the environment is unstable, not because the code is broken.\n\n**Common sources of flakiness**:\n- Timing issues and race conditions\n- Shared state between tests\n- Browser environment differences\n- Network-dependent tests and real external services\n- Time zone, locale, and date-sensitive logic\n\n**Deterministic test patterns**:\n- Use fake timers for scheduled work and debounce/throttle logic\n- Keep async work controlled with explicit promise resolution and digest flushing\n- Reset shared state, mocks, and module caches in `afterEach()`\n- Avoid real browser/network dependencies in unit tests\n- Prefer fixed test data over generated or time-based values\n\n**js-env-sanitizer pattern**:\n- Snapshot and restore environment-dependent globals around each test\n- Isolate `window`, `document`, `localStorage`, `Date`, `Math.random`, feature flags, and DOM mutations\n- This is especially useful in long-lived AngularJS suites where hidden environment coupling causes intermittent failures\n\n## Test Pyramid Guidance for Legacy Codebases\n\nLegacy AngularJS codebases often have an inverted test pyramid: too many end-to-end tests and too few unit tests.\n\n**Recommended shape**:\n- **Base**: many fast unit tests for controllers, services, filters, and directives\n- **Middle**: fewer integration tests for module wiring, routing, and API boundaries\n- **Top**: a small number of end-to-end tests for critical user journeys only\n\n**Guidance**:\n- Shift coverage toward unit tests first\n- Keep integration tests as the middle layer, not the base\n- Use e2e tests sparingly because they are slower and more environment-sensitive\n\n## Test Structure\n\n### Jasmine Test Structure (Legacy Default)\n\nAll Jasmine tests follow this standard structure:\n\n```javascript\ndescribe('Component Name', function() {\n var componentUnderTest, dependencies;\n\n beforeEach(module('myApp'));\n\n beforeEach(inject(function($injector) {\n componentUnderTest = $injector.get('ComponentName');\n dependencies = $injector.get('DependencyName');\n }));\n\n afterEach(function() {\n // Cleanup code\n });\n\n describe('Functionality Group', function() {\n it('should do something specific', function() {\n // Arrange\n var input = 'test';\n\n // Act\n var result = componentUnderTest.method(input);\n\n // Assert\n expect(result).toBe('expected');\n });\n });\n});\n```\n\n### Jest Test Structure (Recommended)\n\nJest tests follow a similar structure with modern mocking and cleaner teardown:\n\n```javascript\ndescribe('Component Name', () => {\n let componentUnderTest;\n let dependency;\n\n beforeEach(() => {\n jest.clearAllMocks();\n // Setup code or mock initialization\n dependency = { method: jest.fn() };\n componentUnderTest = require('./component');\n });\n\n afterEach(() => {\n // Cleanup code\n });\n\n describe('Functionality Group', () => {\n test('should do something specific', () => {\n // Arrange\n const input = 'test';\n\n // Act\n const result = componentUnderTest.method(input, dependency);\n\n // Assert\n expect(result).toBe('expected');\n });\n });\n});\n```\n\n**Key Differences**:\n- Jest uses `jest.fn()` / `jest.spyOn()` instead of Jasmine spies for modern test code\n- Jest uses `test()` or `it()` (both work)\n- Jest auto-discovers `.spec.js` and `.test.js` files\n- Jest provides built-in snapshot testing and code coverage\n\n## Best Practices\n\n### 1. Test Organization\n- One test file per component (e.g., `controller.spec.js` for `controller.js`)\n- Organize tests into logical groups using `describe()`\n- Use meaningful test names that describe expected behavior\n\n### 2. DRY Principle (Don't Repeat Yourself)\n- Extract common setup into `beforeEach()` blocks\n- Create reusable test data fixtures\n- Use helper functions to reduce duplication\n\n### 3. Test Independence\n- Each test should be independent and runnable in any order\n- Clean up resources in `afterEach()`\n- Avoid shared state between tests\n\n### 4. Realistic Mocks\n- Mock external dependencies realistically\n- Use actual data structures when possible\n- Avoid overly simplified or unrealistic mocks\n\n### 5. Comprehensive Coverage\n- Aim for 80%+ code coverage\n- Test happy paths, edge cases, and error scenarios\n- Test async operations and race conditions\n\n### 6. Performance\n- Keep tests fast\n- Use in-memory mocks instead of real HTTP requests\n- Avoid unnecessary database operations\n\n### 7. Maintainability\n- Write tests that are easy to understand\n- Use the AAA pattern for clarity\n- Document complex test logic with comments\n- Refactor tests when the component changes\n\n## Common Testing Scenarios\n\n### Testing Controllers\n\n```javascript\ndescribe('UserController', function() {\n var $scope, controller;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('UserController', {\n $scope: $scope\n });\n }));\n\n it('should initialize with default values', function() {\n expect($scope.users).toBeDefined();\n });\n\n it('should load users on init', function() {\n expect($scope.users.length).toBeGreaterThan(0);\n });\n});\n```\n\n### Testing Services\n\n```javascript\ndescribe('UserService', function() {\n var userService, $httpBackend;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_UserService_, _$httpBackend_) {\n userService = _UserService_;\n $httpBackend = _$httpBackend_;\n }));\n\n afterEach(function() {\n $httpBackend.verifyNoOutstandingExpectation();\n });\n\n it('should fetch users from API', function() {\n var expectedUsers = [{ id: 1, name: 'John' }];\n $httpBackend.expectGET('/api/users').respond(expectedUsers);\n\n userService.getUsers().then(function(users) {\n expect(users).toEqual(expectedUsers);\n });\n\n $httpBackend.flush();\n });\n});\n```\n\n### Testing with Promises\n\n```javascript\ndescribe('PromiseService', function() {\n var service, $q, $rootScope;\n\n beforeEach(inject(function(_Service_, _$q_, _$rootScope_) {\n service = _Service_;\n $q = _$q_;\n $rootScope = _$rootScope_;\n }));\n\n it('should handle promise resolution', function() {\n var deferred = $q.defer();\n var result;\n\n service.asyncOperation().then(function(data) {\n result = data;\n });\n\n deferred.resolve('success');\n $rootScope.$apply();\n\n expect(result).toBe('success');\n });\n});\n```\n\n### Testing Component Directives\n\nAngularJS 1.5+ component directives (`bindings`, `controllerAs`) are the recommended pattern for new code and the easiest to migrate to Angular later.\n\n```javascript\ndescribe('userCard component', function() {\n var $compile, $rootScope, element, scope;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_$compile_, _$rootScope_) {\n $compile = _$compile_;\n $rootScope = _$rootScope_;\n scope = $rootScope.$new();\n scope.user = { name: 'Alice', role: 'admin' };\n }));\n\n it('should render user name and role', function() {\n element = $compile('\u003cuser-card user=\"user\">\u003c/user-card>')(scope);\n scope.$digest();\n\n var isolated = element.isolateScope().$ctrl;\n expect(isolated.user.name).toBe('Alice');\n expect(element.text()).toContain('admin');\n });\n\n it('should call onSelect when clicked', function() {\n scope.onSelect = jasmine.createSpy('onSelect');\n element = $compile('\u003cuser-card user=\"user\" on-select=\"onSelect(user)\">\u003c/user-card>')(scope);\n scope.$digest();\n\n element.isolateScope().$ctrl.onSelect({ user: scope.user });\n expect(scope.onSelect).toHaveBeenCalledWith(scope.user);\n });\n});\n```\n\n### Testing $httpBackend with Request Matchers\n\nFor APIs with dynamic segments or query parameters, use regex or function matchers instead of exact URL strings:\n\n```javascript\n// Match any GET to /api/users with query params\n$httpBackend.expectGET(/\\/api\\/users\\?.*page=/).respond(200, mockResponse);\n\n// Match by function\n$httpBackend.expectGET(function(url) {\n return url.indexOf('/api/users') === 0 && url.indexOf('page=') > -1;\n}).respond(200, mockResponse);\n```\n\n### Testing $on / $broadcast Events\n\n```javascript\ndescribe('event-driven service', function() {\n var $rootScope, service;\n\n beforeEach(inject(function(_$rootScope_, _EventService_) {\n $rootScope = _$rootScope_;\n service = _EventService_;\n }));\n\n it('should react to user:updated event', function() {\n var handler = jasmine.createSpy('handler');\n $rootScope.$on('user:updated', handler);\n\n $rootScope.$broadcast('user:updated', { id: 42 });\n expect(handler).toHaveBeenCalled();\n expect(handler.calls.argsFor(0)[1]).toEqual({ id: 42 });\n });\n});\n```\n\n## Debugging Tests\n\n### Jasmine Debugging\n\n```javascript\n// Skip a test\nxit('should do something', function() { ... });\n\n// Run only this test\nfit('should do something', function() { ... });\n\n// Console logging in tests\nit('should debug', function() {\n console.log('Current state:', $scope);\n expect(true).toBe(true);\n});\n```\n\n### Jest Debugging\n\n```javascript\n// Skip a test\ntest.skip('should do something', () => { ... });\n\n// Run only this test\ntest.only('should do something', () => { ... });\n\n// Debug with Node inspector\n// Run: node --inspect-brk node_modules/.bin/jest --runInBand\n```\n\n## Integration with CI/CD\n\n### GitHub Actions Example\n\n```yaml\nname: Test\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '22'\n cache: npm\n - run: npm ci\n - run: npm test -- --coverage\n - uses: codecov/codecov-action@v4\n with:\n files: ./coverage/lcov.info\n```\n\n### Jenkins Example\n\n```groovy\npipeline {\n stages {\n stage('Test') {\n steps {\n sh 'npm ci'\n sh 'npm test'\n publishHTML([\n reportDir: 'coverage',\n reportFiles: 'index.html',\n reportName: 'Coverage Report'\n ])\n }\n }\n }\n}\n```\n\n## Resources\n\n- [Jasmine Documentation](https://jasmine.github.io/)\n- [Karma Test Runner](https://karma-runner.github.io/)\n- [Jest Documentation](https://jestjs.io/)\n- [jest-preset-angular](https://github.com/thymikee/jest-preset-angular)\n- [AngularJS Testing Guide](https://docs.angularjs.org/guide/unit-testing)\n- [Angular Testing Guide](https://angular.dev/guide/testing)\n- [Angular Update Guide](https://angular.dev/update-guide)\n\n## Troubleshooting\n\n### Tests not running\n- Check that module is loaded: `beforeEach(module('myApp'))`\n- Verify dependencies are injected: `beforeEach(inject(...))`\n- Check for syntax errors\n\n### Async tests timing out\n- Ensure promises are resolved: `$rootScope.$apply()` or `$httpBackend.flush()`\n- Use `done()` callback: `it('...', function(done) { ... done(); })`\n- For Jest: use `async/await` or return a promise\n\n### HTTP mocks not working\n- Verify mock is set up before service call\n- Check exact URL match in expectations\n- Use `passThrough()` for unmocked requests\n\n### Coverage gaps\n- Review untested branches in coverage reports\n- Test error conditions and edge cases\n- Mock external dependencies properly\n\n## Modern Runtime Compatibility\n\nAngularJS was built for an older Node.js and browser ecosystem, but it still runs on modern runtimes with the right configuration.\n\n### Node.js 22 LTS\n- AngularJS 1.8.x runs on Node 22 LTS without modification\n- If your tests use Karma with a real browser, use `karma-chrome-launcher` with headless Chrome\n- For Jest: `testEnvironment: 'jsdom'` handles the browser globals — no real browser needed\n\n### Headless Chrome in CI\n```yaml\n# GitHub Actions — headless Chrome with Karma\n- uses: actions/setup-chrome@v1\n with:\n chrome-version: stable\n- run: npm test\n```\n\n```javascript\n// karma.conf.js — headless Chrome for CI\nbrowsers: ['ChromeHeadlessNoSandbox'],\ncustomLaunchers: {\n ChromeHeadlessNoSandbox: {\n base: 'ChromeHeadless',\n flags: ['--no-sandbox']\n }\n}\n```\n\n### Common Pitfalls on Modern Runtimes\n- **V8 strict mode**: AngularJS code relying on implicit globals or `arguments.callee` will fail in strict mode. Use `'use strict'` in test files to catch these early.\n- **jsdom vs. real browser**: Some directive tests that depend on real layout or CSS computation may not work in jsdom. Run those with Karma + headless Chrome instead.\n- **npm audit failures**: AngularJS dependencies may have known vulnerabilities. Use `npm audit --production` to separate real risks from dev-only warnings, and consider `overrides` in `package.json` to pin patched transitive dependencies.\n\n## Migration Path: AngularJS → Angular\n\nWhen the time comes to move off AngularJS, here is the conceptual mapping:\n\n| AngularJS | Angular 19+ |\n|---|---|\n| Modules / controllers | Standalone components or NgModules, injectable services |\n| `$scope` / `$rootScope` | Component state, `@Input()` / `@Output()`, signals |\n| `$http` / `$resource` | `HttpClient` |\n| Directives | Components and directives with modern APIs |\n| `$q` / digest cycle | RxJS, promises/async-await, signals |\n| `$routeProvider` | Angular Router |\n| `angular.module()` DI | Tree-shakable providers, `inject()` |\n| Globals and ad-hoc DOM | Dependency injection and testable abstractions |\n\n**Hybrid migration**: Use `@angular/upgrade` to run AngularJS and Angular side by side. Migrate component-by-component rather than rewriting the whole app at once. The AngularJS test suite stays active throughout — Jest is the best runner for hybrid repos because it handles both AngularJS and Angular test files.\n\n## Next Steps\n\n1. **Start Small**: Write tests for a single component\n2. **Choose the Right Runner**: Keep Jasmine/Karma only for existing suites; prefer Jest for new work and hybrid repos\n3. **Understand Patterns**: Study the testing patterns guide\n4. **Use Templates**: Reference template files for your component type\n5. **Refine**: Improve tests based on coverage and feedback\n6. **Automate**: Integrate tests into your CI/CD pipeline\n7. **Plan the Migration**: When ready, use `@angular/upgrade` for incremental migration — your test suite migrates with you\n\n---\n\n**Specialization**: AngularJS Unit Testing with Jasmine and Jest \n**Version**: 2.0 \n**Last Updated**: May 2026\n---","attachment_filenames":["INDEX.md","README.md","resources/angularjs-testing-reference.md","resources/best-practices-checklist.md","resources/coverage-analysis-guide.md","resources/http-mocking-guide.md","resources/jest-migration-guide.md","resources/mock-helpers-guide.md","resources/testing-patterns-guide.md","scripts/run-tests.sh"],"attachments":[{"filename":"INDEX.md","content":"# AngularJS Unit Testing Skill - Navigation Index\n\n**Version**: 1.2 | **Last Updated**: January 10, 2026\n\n## Quick Navigation\n\n### 📚 **Main Documentation**\n- [Skill Overview](../skill.md) - Complete skill documentation with Jasmine + Jest support\n\n### 📖 **Resource Guides** (Comprehensive References)\n\n#### Core References\n- [AngularJS Testing Reference](./resources/angularjs-testing-reference.md) - Complete API reference for testing AngularJS components\n- [Testing Patterns Guide](./resources/testing-patterns-guide.md) - 8+ proven testing patterns with examples\n\n#### Advanced Topics\n- [HTTP Mocking Guide](./resources/http-mocking-guide.md) - Complete guide to $httpBackend mocking\n- [Mock Helpers & Spies](./resources/mock-helpers-guide.md) - Service mocking and spy techniques\n- [Code Coverage Analysis](./resources/coverage-analysis-guide.md) - Coverage metrics and improvement strategies\n- [Jest Migration Guide](./resources/jest-migration-guide.md) - Guide for moving from Jasmine to Jest\n\n#### Best Practices\n- [Best Practices Checklist](./resources/best-practices-checklist.md) - Quality assurance checklist and scoring\n\n### 📝 **Test Templates** (Copy & Paste Ready)\n\n- [controller.spec.js](./templates/controller.spec.js) - Full controller testing template (500+ lines)\n- [service.spec.js](./templates/service.spec.js) - Full service testing template (600+ lines)\n- [directive.spec.js](./templates/directive.spec.js) - Directive testing template\n- [filter.spec.js](./templates/filter.spec.js) - Filter testing template\n- [fixtures.js](./templates/fixtures.js) - Reusable test fixtures and helpers\n\n### 🔧 **Scripts**\n\n- [run-tests.sh](./scripts/run-tests.sh) - Test execution script with coverage support\n\n---\n\n## Getting Started\n\n### For New AngularJS Projects\n1. Read [Skill Overview](../skill.md)\n2. Review [Testing Patterns Guide](./resources/testing-patterns-guide.md)\n3. Use templates as starting points\n4. Reference [Best Practices Checklist](./resources/best-practices-checklist.md)\n\n### For Jasmine Users\n1. Start with [AngularJS Testing Reference](./resources/angularjs-testing-reference.md)\n2. Copy templates from `/templates/`\n3. Follow [Testing Patterns Guide](./resources/testing-patterns-guide.md)\n\n### For Jest Users\n1. Read [Jest Migration Guide](./resources/jest-migration-guide.md)\n2. Use Jest configuration section in [Skill Overview](../skill.md)\n3. Adapt templates as needed\n\n### For HTTP Testing\n1. Follow [HTTP Mocking Guide](./resources/http-mocking-guide.md)\n2. See examples in [service.spec.js](./templates/service.spec.js)\n\n### For Coverage Analysis\n1. Review [Code Coverage Analysis](./resources/coverage-analysis-guide.md)\n2. Run tests with coverage: `./scripts/run-tests.sh --coverage`\n\n---\n\n## Common Tasks\n\n| Task | Resource |\n|------|----------|\n| **Write controller tests** | [controller.spec.js](./templates/controller.spec.js) + [Testing Patterns](./resources/testing-patterns-guide.md) |\n| **Write service tests** | [service.spec.js](./templates/service.spec.js) + [HTTP Mocking Guide](./resources/http-mocking-guide.md) |\n| **Mock services** | [Mock Helpers Guide](./resources/mock-helpers-guide.md) + [fixtures.js](./templates/fixtures.js) |\n| **Test HTTP calls** | [HTTP Mocking Guide](./resources/http-mocking-guide.md) |\n| **Improve coverage** | [Code Coverage Analysis](./resources/coverage-analysis-guide.md) |\n| **Quality assurance** | [Best Practices Checklist](./resources/best-practices-checklist.md) |\n| **Migrate to Jest** | [Jest Migration Guide](./resources/jest-migration-guide.md) |\n| **Run tests** | [run-tests.sh](./scripts/run-tests.sh) |\n\n---\n\n## Framework Comparison\n\n### Jasmine (Traditional, Recommended for AngularJS 1.x)\n- Browser-based testing via Karma\n- Tight AngularJS integration\n- Mature ecosystem\n- See all Jasmine content throughout resources\n\n### Jest (Modern, For New/Modern Projects)\n- Node.js-based testing\n- Fast parallel execution\n- Built-in mocking and coverage\n- Snapshot testing support\n- See [Jest Migration Guide](./resources/jest-migration-guide.md)\n\n---\n\n## File Organization\n\n```\nangularjs-unit-testing-skill/\n├── README.md (This file)\n├── skill.md (Main documentation)\n├── resources/ (Comprehensive guides)\n│ ├── angularjs-testing-reference.md\n│ ├── testing-patterns-guide.md\n│ ├── http-mocking-guide.md\n│ ├── mock-helpers-guide.md\n│ ├── coverage-analysis-guide.md\n│ ├── jest-migration-guide.md\n│ └── best-practices-checklist.md\n├── templates/ (Copy-paste ready templates)\n│ ├── controller.spec.js (500+ lines, fully commented)\n│ ├── service.spec.js (600+ lines, fully commented)\n│ ├── directive.spec.js\n│ ├── filter.spec.js\n│ └── fixtures.js (Mock data and helpers)\n└── scripts/ (Automation)\n └── run-tests.sh (Test runner with coverage)\n```\n\n---\n\n## Quick Reference\n\n### Common Test Structure (Jasmine)\n```javascript\ndescribe('Component', function() {\n var component, $httpBackend;\n\n beforeEach(module('app'));\n beforeEach(inject(function(_Component_, _$httpBackend_) {\n component = _Component_;\n $httpBackend = _$httpBackend_;\n }));\n\n it('should do something', function() {\n expect(component).toBeDefined();\n });\n});\n```\n\n### Common Test Structure (Jest)\n```javascript\ndescribe('Component', () => {\n let component;\n\n beforeEach(() => {\n component = new Component();\n });\n\n test('should do something', () => {\n expect(component).toBeDefined();\n });\n});\n```\n\n---\n\n## Documentation Stats\n\n- **Total Documentation**: 27,500+ words\n- **Code Examples**: 200+ examples with dual-framework support\n- **Test Templates**: 5 production-ready templates (2,000+ lines)\n- **Resource Guides**: 7 comprehensive guides\n- **Test Patterns**: 8+ documented patterns\n- **Coverage**: Jasmine + Jest support throughout\n\n---\n\n## Support & References\n\n- [AngularJS Documentation](https://docs.angularjs.org/)\n- [Jasmine Documentation](https://jasmine.github.io/)\n- [Jest Documentation](https://jestjs.io/)\n- [Karma Test Runner](https://karma-runner.github.io/)\n- [Istanbul/NYC Coverage](https://istanbul.js.org/)\n\n---\n\n## Version History\n\n- **v1.2** - Added Jest migration guide and expanded mock helpers (Jan 10, 2026)\n- **v1.1** - Added Jasmine + Jest dual-framework support (Jan 8, 2026)\n- **v1.0** - Initial release with Jasmine-focused content (Jan 1, 2026)\n\n---\n\n**Need help?** Review the [Best Practices Checklist](./resources/best-practices-checklist.md) or start with a template matching your component type.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":6785,"content_sha256":"fc8284ae7b88d491cf407ba4c290e6e77d353c04182b303f0a3d2b8c04a0bc7f"},{"filename":"README.md","content":"# AngularJS Unit Testing Skill - Completion Report\n\n**Status**: ✅ **COMPLETE** \n**Date**: January 10, 2026 \n**Version**: 1.2\n\n---\n\n## Executive Summary\n\nA comprehensive, production-ready AngularJS unit testing skill package has been successfully created with complete support for both **Jasmine** and **Jest** testing frameworks.\n\n**Total Deliverables**: 16 files | **6,704 lines of code and documentation**\n\n---\n\n## What's Included\n\n### 📚 **Main Documentation**\n- **skill.md** (6,000+ words)\n - Complete skill overview\n - AngularJS component testing guide\n - Jasmine + Jest framework comparison\n - 10+ real-world testing scenarios\n - Debugging strategies and troubleshooting\n - CI/CD integration examples\n\n### 📖 **Resource Guides** (7 comprehensive references)\n\n| Guide | Lines | Purpose |\n|-------|-------|---------|\n| **AngularJS Testing Reference** | 500+ | Complete API reference for testing AngularJS components |\n| **Testing Patterns Guide** | 600+ | 8+ proven patterns with dual-framework examples |\n| **HTTP Mocking Guide** | 504 | Complete $httpBackend mocking patterns |\n| **Mock Helpers & Spies** | 400+ | Service mocking and spy techniques |\n| **Code Coverage Analysis** | 350+ | Coverage metrics and improvement strategies |\n| **Jest Migration Guide** | 400+ | Comprehensive guide for Jasmine-to-Jest migration |\n| **Best Practices Checklist** | 300+ | Quality assurance checklist with scoring |\n\n**Total**: 3,450+ lines of reference documentation\n\n### 📝 **Test Templates** (5 production-ready templates)\n\n| Template | Lines | Purpose |\n|----------|-------|---------|\n| **controller.spec.js** | 500+ | Full controller testing (Jasmine + Jest) |\n| **service.spec.js** | 600+ | Full service testing with HTTP mocking |\n| **directive.spec.js** | 150+ | Directive testing template |\n| **filter.spec.js** | 100+ | Filter testing template |\n| **fixtures.js** | 400+ | Reusable test data and helpers |\n\n**Total**: 1,750+ lines of production-ready code\n\n### 🔧 **Automation Scripts**\n- **run-tests.sh** (200+ lines)\n - Automated test execution\n - Coverage report generation\n - Watch mode support\n - CI/CD integration ready\n\n### 🧭 **Navigation**\n- **INDEX.md** - Complete navigation guide\n- **README.md** (This file)\n\n---\n\n## Framework Support\n\n### ✅ Jasmine (Recommended for AngularJS 1.x)\n- Complete API reference\n- Karma integration guide\n- $httpBackend mocking patterns\n- Spy and mock techniques\n- All templates with Jasmine examples\n\n### ✅ Jest (Modern alternative)\n- Complete setup guide\n- Jest-specific mocking patterns\n- Migration guide from Jasmine\n- All templates with Jest examples\n- Snapshot testing information\n\n---\n\n## Key Features\n\n### Comprehensive Coverage\n- **Controllers**: Full lifecycle testing, scope management, events\n- **Services**: HTTP mocking, promises, error handling, caching\n- **Directives**: DOM manipulation, scope isolation, event handling\n- **Filters**: Pure function testing with edge cases\n- **Promises**: Async operations, resolution, rejection handling\n- **HTTP**: Request mocking, response handling, error scenarios\n\n### Production-Ready Code\n- 200+ code examples with real-world scenarios\n- Both Jasmine and Jest implementations\n- Properly commented and documented\n- Best practices integrated throughout\n- Error handling and edge cases covered\n\n### Developer Experience\n- Copy-paste ready templates\n- Clear setup instructions\n- Troubleshooting guides\n- Performance tips and tricks\n- Common pitfalls and solutions\n\n---\n\n## File Structure\n\n```\nangularjs-unit-testing-skill/\n├── README.md ← You are here\n├── INDEX.md ← Navigation guide\n├── skill.md ← Main documentation\n│\n├── resources/ ← Reference guides\n│ ├── angularjs-testing-reference.md\n│ ├── testing-patterns-guide.md\n│ ├── http-mocking-guide.md\n│ ├── mock-helpers-guide.md\n│ ├── coverage-analysis-guide.md\n│ ├── jest-migration-guide.md\n│ └── best-practices-checklist.md\n│\n├── templates/ ← Copy-paste ready\n│ ├── controller.spec.js (500+ lines, fully commented)\n│ ├── service.spec.js (600+ lines, fully commented)\n│ ├── directive.spec.js\n│ ├── filter.spec.js\n│ └── fixtures.js (Mock data and helpers)\n│\n└── scripts/ ← Automation\n └── run-tests.sh (Test runner with coverage)\n```\n\n---\n\n## Quick Start\n\n### For Jasmine Users\n1. Read [skill.md](./skill.md) - Jasmine sections\n2. Copy [controller.spec.js](./templates/controller.spec.js) template\n3. Reference [AngularJS Testing Reference](./resources/angularjs-testing-reference.md)\n4. Follow [Best Practices Checklist](./resources/best-practices-checklist.md)\n\n### For Jest Users\n1. Read [Jest Migration Guide](./resources/jest-migration-guide.md)\n2. Review Jest setup section in [skill.md](./skill.md)\n3. Adapt templates from `/templates/`\n4. Use Jest-specific mocking patterns\n\n### For HTTP Testing\n1. Study [HTTP Mocking Guide](./resources/http-mocking-guide.md)\n2. See real examples in [service.spec.js](./templates/service.spec.js)\n3. Use fixtures from [fixtures.js](./templates/fixtures.js)\n\n---\n\n## Statistics\n\n| Metric | Value |\n|--------|-------|\n| **Total Files** | 16 |\n| **Total Lines** | 6,704 |\n| **Documentation Files** | 8 |\n| **Test Templates** | 5 |\n| **Automation Scripts** | 1 |\n| **Navigation Guides** | 2 |\n| **Total Words** | 27,500+ |\n| **Code Examples** | 200+ |\n| **Test Patterns** | 8+ |\n\n---\n\n## Feature Matrix\n\n### Components Covered\n- ✅ Controllers (initialization, methods, scope, events)\n- ✅ Services (GET, POST, PUT, DELETE, promises)\n- ✅ Directives (compilation, scope, DOM manipulation)\n- ✅ Filters (pure functions, edge cases)\n- ✅ Routes (navigation, parameters)\n- ✅ HTTP (mocking, errors, timeouts)\n\n### Test Scenarios\n- ✅ Happy path testing\n- ✅ Error handling and edge cases\n- ✅ Async operations and promises\n- ✅ Scope and event handling\n- ✅ HTTP request/response mocking\n- ✅ Service dependencies and mocking\n- ✅ Performance and coverage\n\n### Best Practices Included\n- ✅ AAA Pattern (Arrange-Act-Assert)\n- ✅ Given-When-Then (BDD)\n- ✅ Test fixtures and helpers\n- ✅ Mock and spy techniques\n- ✅ Coverage analysis\n- ✅ CI/CD integration\n- ✅ Debugging strategies\n\n---\n\n## Usage Examples\n\n### Running Tests\n```bash\n# Run all tests\n./scripts/run-tests.sh\n\n# Watch mode\n./scripts/run-tests.sh --watch\n\n# With coverage\n./scripts/run-tests.sh --coverage\n\n# Specific browser\n./scripts/run-tests.sh --browsers Chrome,Firefox\n```\n\n### Using Templates\n```bash\n# Copy controller template\ncp templates/controller.spec.js src/components/my-component.spec.js\n\n# Copy service template\ncp templates/service.spec.js src/services/my-service.spec.js\n\n# Copy fixtures\ncp templates/fixtures.js test/fixtures.js\n```\n\n### Referencing Documentation\n```javascript\n// When writing tests:\n// 1. Check the appropriate template\n// 2. Reference AngularJS Testing Reference for API\n// 3. Follow Testing Patterns Guide\n// 4. Use Best Practices Checklist\n```\n\n---\n\n## Version History\n\n### v1.2 (January 10, 2026)\n- ✅ Added Jest migration guide\n- ✅ Expanded mock helpers guide with advanced spying\n- ✅ Created comprehensive coverage analysis guide\n- ✅ Added best practices checklist with scoring\n- ✅ Created INDEX.md navigation guide\n- ✅ Finalized all 16 files\n\n### v1.1 (January 8, 2026)\n- ✅ Added Jasmine + Jest dual-framework support\n- ✅ Enhanced all templates with both frameworks\n- ✅ Created testing patterns guide\n- ✅ Created HTTP mocking guide\n\n### v1.0 (January 1, 2026)\n- ✅ Initial skill creation\n- ✅ Main documentation with Jasmine focus\n- ✅ Core test templates\n- ✅ Basic fixtures and helpers\n\n---\n\n## Quality Assurance\n\n### Documentation Quality\n- ✅ 200+ code examples with explanations\n- ✅ Real-world scenarios and patterns\n- ✅ Both Jasmine and Jest support\n- ✅ Comprehensive table of contents\n- ✅ Clear navigation structure\n\n### Code Quality\n- ✅ Production-ready templates\n- ✅ Proper error handling\n- ✅ Extensive comments explaining logic\n- ✅ Best practices integrated\n- ✅ Edge cases covered\n\n### Test Coverage\n- ✅ Happy path scenarios\n- ✅ Error scenarios\n- ✅ Edge cases and boundary values\n- ✅ Async operations\n- ✅ Integration scenarios\n\n---\n\n## Support Resources\n\n### Internal Resources\n- [Main Skill Documentation](./skill.md)\n- [Navigation Index](./INDEX.md)\n- [Best Practices Checklist](./resources/best-practices-checklist.md)\n\n### External Resources\n- [AngularJS Documentation](https://docs.angularjs.org/)\n- [Jasmine Documentation](https://jasmine.github.io/)\n- [Jest Documentation](https://jestjs.io/)\n- [Karma Test Runner](https://karma-runner.github.io/)\n\n---\n\n## Next Steps\n\n1. **Review** - Start with [skill.md](./skill.md)\n2. **Choose Framework** - Jasmine or Jest?\n3. **Select Template** - Pick appropriate template\n4. **Implement Tests** - Use templates and guides\n5. **Check Quality** - Use best practices checklist\n6. **Measure Coverage** - Run coverage reports\n\n---\n\n## Troubleshooting\n\n### Common Issues\n| Issue | Solution |\n|-------|----------|\n| Tests not running | Check [skill.md](./skill.md) setup section |\n| HTTP mocking errors | Review [HTTP Mocking Guide](./resources/http-mocking-guide.md) |\n| Low coverage | Use [Coverage Analysis Guide](./resources/coverage-analysis-guide.md) |\n| Framework confusion | Read [Jest Migration Guide](./resources/jest-migration-guide.md) |\n\n### Getting Help\n1. Check relevant resource guide\n2. Review Best Practices Checklist\n3. Examine template examples\n4. Consult troubleshooting sections in skill.md\n\n---\n\n## License & Usage\n\nThis skill package is designed to be:\n- ✅ Copied and adapted for your projects\n- ✅ Referenced in documentation\n- ✅ Shared with team members\n- ✅ Integrated into CI/CD pipelines\n- ✅ Extended with custom patterns\n\n---\n\n## Acknowledgments\n\nCreated as a comprehensive resource for AngularJS developers learning and implementing unit testing best practices with both Jasmine and Jest frameworks.\n\n---\n\n## Contact & Feedback\n\nFor issues, improvements, or feedback on this skill package, refer to the main documentation and resource guides.\n\n---\n\n**AngularJS Unit Testing Skill v1.2** \n*A comprehensive, production-ready guide to testing AngularJS applications with Jasmine and Jest*\n\n**Created**: January 10, 2026 \n**Status**: Complete and Ready for Use ✅\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10682,"content_sha256":"e9f6ced4943f3a2cdf81fb38fa5ec2bc20daafca962689080b418fe125f89e99"},{"filename":"resources/angularjs-testing-reference.md","content":"# AngularJS Testing Reference Guide\n\n**Frameworks**: Jasmine (recommended) and Jest\n\nThis reference covers APIs available in both Jasmine and Jest. Most APIs are identical or have direct equivalents.\n\n## Jasmine API Reference\n\n### Test Structure Functions\n\n| Function | Purpose | Example |\n|----------|---------|---------|\n| `describe(name, fn)` | Group tests into a suite | `describe('UserController', fn)` |\n| `it(name, fn)` | Define a single test | `it('should initialize', fn)` |\n| `beforeEach(fn)` | Run before each test | `beforeEach(function() { ... })` |\n| `afterEach(fn)` | Run after each test | `afterEach(function() { ... })` |\n| `beforeAll(fn)` | Run once before all tests | `beforeAll(function() { ... })` |\n| `afterAll(fn)` | Run once after all tests | `afterAll(function() { ... })` |\n\n### Expectations (Assertions)\n\n| Matcher | Purpose | Example |\n|---------|---------|---------|\n| `toBe()` | Test exact equality (===) | `expect(result).toBe(42)` |\n| `toEqual()` | Test deep equality | `expect(obj).toEqual({a: 1})` |\n| `toMatch()` | Test regex match | `expect(text).toMatch(/pattern/)` |\n| `toBeDefined()` | Test if defined | `expect(value).toBeDefined()` |\n| `toBeUndefined()` | Test if undefined | `expect(value).toBeUndefined()` |\n| `toBeNull()` | Test if null | `expect(value).toBeNull()` |\n| `toBeTruthy()` | Test truthy value | `expect(value).toBeTruthy()` |\n| `toBeFalsy()` | Test falsy value | `expect(value).toBeFalsy()` |\n| `toContain()` | Test array/string contains | `expect(arr).toContain(item)` |\n| `toThrow()` | Test function throws error | `expect(fn).toThrow()` |\n| `toThrowError()` | Test specific error | `expect(fn).toThrowError(Error)` |\n| `toHaveBeenCalled()` | Test spy was called | `expect(spy).toHaveBeenCalled()` |\n| `toHaveBeenCalledWith()` | Test spy called with args | `expect(spy).toHaveBeenCalledWith(arg)` |\n| `toHaveBeenCalledTimes()` | Test spy call count | `expect(spy).toHaveBeenCalledTimes(2)` |\n| `toHaveClass()` | Test DOM element has class | `expect(element).toHaveClass('active')` |\n\n### Negation\n\nAdd `.not` before any matcher to negate it:\n\n```javascript\nexpect(result).not.toBe(expected);\nexpect(result).not.toEqual(obj);\nexpect(result).not.toHaveBeenCalled();\n```\n\n## Spies\n\n### Creating Spies (Jasmine)\n\n```javascript\n// Spy on existing function\nspyOn(object, 'methodName');\n\n// Spy with return value\nspyOn(object, 'methodName').and.returnValue('value');\n\n// Spy that calls through to original\nspyOn(object, 'methodName').and.callThrough();\n\n// Spy that throws error\nspyOn(object, 'methodName').and.throwError('error');\n\n// Spy that calls fake implementation\nspyOn(object, 'methodName').and.callFake(function(arg) {\n return arg * 2;\n});\n\n// Create standalone spy\nvar spy = jasmine.createSpy('spyName');\nvar spy = jasmine.createSpy('spyName').and.returnValue('value');\n\n// Create spy object with multiple methods\nvar mockObj = jasmine.createSpyObj('mockName', ['method1', 'method2']);\n```\n\n### Spy Matchers\n\n```javascript\n// Verify spy was called\nexpect(spy).toHaveBeenCalled();\n\n// Verify spy was called N times\nexpect(spy).toHaveBeenCalledTimes(3);\n\n// Verify spy was called with specific arguments\nexpect(spy).toHaveBeenCalledWith(arg1, arg2);\n\n// Get call info\nexpect(spy.calls.count()).toBe(2);\nexpect(spy.calls.argsFor(0)).toEqual([arg1, arg2]);\n```\n\n## AngularJS Testing Utilities\n\n### Module Loading\n\n```javascript\n// Load module\nbeforeEach(module('myApp'));\n\n// Load multiple modules\nbeforeEach(module('myApp', 'ngMock'));\n\n// Configure module before injection\nbeforeEach(module(function($provide) {\n $provide.value('service', mockService);\n}));\n```\n\n### Dependency Injection\n\n```javascript\n// Inject dependencies\nbeforeEach(inject(function($injector) {\n var service = $injector.get('ServiceName');\n}));\n\n// Inject with underscore wrapping (recommended)\nbeforeEach(inject(function(_$httpBackend_, _ServiceName_) {\n $httpBackend = _$httpBackend_;\n service = _ServiceName_;\n}));\n\n// Get service directly\nvar service = inject(function($injector) {\n return $injector.get('ServiceName');\n});\n```\n\n### $httpBackend\n\n```javascript\n// Expect HTTP method\n$httpBackend.expectGET('/api/users');\n$httpBackend.expectPOST('/api/users', data);\n$httpBackend.expectPUT('/api/users/1', data);\n$httpBackend.expectDELETE('/api/users/1');\n$httpBackend.expectPATCH('/api/users/1', data);\n\n// Define response\n$httpBackend.expectGET('/api/users').respond([users]);\n$httpBackend.expectGET('/api/users').respond(200, [users]);\n$httpBackend.expectGET('/api/users').respond(500, 'error');\n\n// Match URL patterns\n$httpBackend.expectGET(/^\\/api\\/users\\/.+$/).respond([]);\n\n// Flush pending requests\n$httpBackend.flush();\n\n// Verify no outstanding requests\n$httpBackend.verifyNoOutstandingExpectation();\n$httpBackend.verifyNoOutstandingRequest();\n```\n\n### $scope Testing\n\n```javascript\n// Create scope\nvar $scope = $rootScope.$new();\n\n// Apply changes\n$scope.name = 'test';\n$rootScope.$apply();\n\n// Watch for changes\n$scope.$watch('name', function(newVal, oldVal) {\n console.log('Changed:', newVal);\n});\n\n// Trigger digest cycle\n$rootScope.$digest();\n```\n\n### $timeout Testing\n\n```javascript\n// Mock timeout\nbeforeEach(inject(function(_$timeout_) {\n $timeout = _$timeout_;\n}));\n\n// After calling timeout code\n$timeout.flush(); // Execute all pending timeouts\n\n// Verify timeout was called\nexpect($timeout.verifyNoPendingTimers);\n```\n\n### Promise Testing\n\n```javascript\n// Inject $q and $rootScope\nbeforeEach(inject(function(_$q_, _$rootScope_) {\n $q = _$q_;\n $rootScope = _$rootScope_;\n}));\n\n// Create deferred object\nvar deferred = $q.defer();\nvar promise = deferred.promise;\n\n// Test resolution\npromise.then(function(result) {\n expect(result).toBe('success');\n});\n\ndeferred.resolve('success');\n$rootScope.$apply();\n\n// Test rejection\npromise.catch(function(error) {\n expect(error).toBe('error');\n});\n\ndeferred.reject('error');\n$rootScope.$apply();\n```\n\n## Jest API Reference\n\n### Test Structure Functions\n\n| Function | Purpose | Example |\n|----------|---------|---------|\n| `describe(name, fn)` | Group tests into a suite | `describe('Component', fn)` |\n| `test(name, fn)` | Define a single test | `test('should work', fn)` |\n| `it(name, fn)` | Alias for test() | `it('should work', fn)` |\n| `beforeEach(fn)` | Run before each test | `beforeEach(() => { ... })` |\n| `afterEach(fn)` | Run after each test | `afterEach(() => { ... })` |\n| `beforeAll(fn)` | Run once before all tests | `beforeAll(() => { ... })` |\n| `afterAll(fn)` | Run once after all tests | `afterAll(() => { ... })` |\n\n### Mock Functions\n\n```javascript\n// Create mock function\nconst mock = jest.fn();\n\n// Mock with return value\nconst mock = jest.fn().mockReturnValue('value');\n\n// Mock with implementation\nconst mock = jest.fn((arg) => arg * 2);\n\n// Mock resolved value (Promise)\nconst mock = jest.fn().mockResolvedValue('success');\n\n// Mock rejected value (Promise)\nconst mock = jest.fn().mockRejectedValue('error');\n\n// Get call info\nexpect(mock).toHaveBeenCalled();\nexpect(mock).toHaveBeenCalledWith(arg);\nexpect(mock).toHaveBeenCalledTimes(2);\nexpect(mock.mock.calls[0]).toEqual([arg1, arg2]);\n```\n\n### Module Mocking\n\n```javascript\n// Mock entire module\njest.mock('./module');\n\n// Mock with manual implementation\njest.mock('./module', () => ({\n method: jest.fn().mockReturnValue('value')\n}));\n\n// Clear mocks\njest.clearAllMocks();\njest.resetAllMocks();\n```\n\n### Snapshot Testing\n\n```javascript\n// Create snapshot\nexpect(component).toMatchSnapshot();\n\n// Update snapshots (--updateSnapshot or -u flag)\njest.updateSnapshot();\n```\n\n## Comparison: Jasmine vs Jest\n\n| Task | Jasmine | Jest |\n|------|---------|------|\n| **Mock function** | `jasmine.createSpy()` | `jest.fn()` |\n| **Mock module** | Manual with `$provide` | `jest.mock()` |\n| **HTTP mock** | `$httpBackend` | `jest.mock('axios')` |\n| **Async test** | `done()` callback | `async/await` |\n| **Coverage** | Istanbul plugin | Built-in |\n| **Snapshots** | Not supported | Supported |\n\n## Common Patterns\n\n### Controller Testing Pattern\n\n```javascript\ndescribe('MyController', function() {\n var $scope, controller;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('MyController', {\n $scope: $scope\n });\n }));\n\n it('should initialize', function() {\n expect($scope.property).toBeDefined();\n });\n});\n```\n\n### Service Testing Pattern\n\n```javascript\ndescribe('MyService', function() {\n var service, $httpBackend;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_MyService_, _$httpBackend_) {\n service = _MyService_;\n $httpBackend = _$httpBackend_;\n }));\n\n afterEach(function() {\n $httpBackend.verifyNoOutstandingExpectation();\n });\n\n it('should fetch data', function() {\n $httpBackend.expectGET('/api/data').respond([]);\n service.getData();\n $httpBackend.flush();\n });\n});\n```\n\n## Tips & Tricks\n\n1. **Use underscores for injection**: `_$httpBackend_` prevents variable shadowing\n2. **Flush HTTP calls**: Always call `$httpBackend.flush()` after HTTP expectations\n3. **Apply changes**: Call `$rootScope.$apply()` to trigger digest cycle\n4. **Test async**: Use callbacks or promises depending on the service\n5. **Mock realistically**: Mock services should behave like real services\n6. **Isolate tests**: Each test should be independent\n7. **Group related tests**: Use nested `describe()` blocks\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":9463,"content_sha256":"d61c6bbbb2f3c732a101db209366c3c12d596e048721240614fe04c482d04a6a"},{"filename":"resources/best-practices-checklist.md","content":"# Best Practices Checklist for AngularJS Testing\n\n## Pre-Test Planning\n\n- [ ] **Requirements Clear**: Understand what component should do\n- [ ] **Edge Cases Identified**: List boundary conditions and error scenarios\n- [ ] **Dependencies Mapped**: Know all external dependencies\n- [ ] **Data Models Defined**: Understand data structures\n- [ ] **API Contracts Known**: Know HTTP endpoints and data format\n- [ ] **Error Scenarios Listed**: Identify all error cases\n- [ ] **Performance Targets Set**: Define acceptable test execution time\n\n## Test Organization\n\n- [ ] **One Test File Per Component**: Named `component.spec.js`\n- [ ] **Logical Grouping**: Use `describe()` blocks for organization\n- [ ] **Clear Test Names**: Names describe expected behavior\n- [ ] **DRY Setup**: Common setup in `beforeEach()`\n- [ ] **Clean Teardown**: Resources cleaned up in `afterEach()`\n- [ ] **Independence**: Each test runs independently\n- [ ] **No Global State**: Tests don't share state\n\n## Test Structure\n\n- [ ] **AAA Pattern**: Arrange-Act-Assert clearly visible\n- [ ] **Single Responsibility**: One concept per test\n- [ ] **Readable**: Easy to understand intent\n- [ ] **No Comments**: Code is self-explanatory\n- [ ] **Meaningful Assertions**: Verify intended behavior\n- [ ] **Consistent Naming**: Same conventions throughout\n- [ ] **Proper Indentation**: Code is properly formatted\n\n## Mocking & Dependency Injection\n\n- [ ] **External Dependencies Mocked**: HTTP, external services\n- [ ] **Realistic Mocks**: Mocks behave like real services\n- [ ] **Dependencies Injected**: Using AngularJS injection\n- [ ] **$provide Used Correctly**: For overriding services\n- [ ] **Spy Verification**: Verify service interactions\n- [ ] **Mock Data Consistent**: Same data structures as real services\n- [ ] **Spies Reset**: Clear mocks between tests\n\n## HTTP Testing\n\n- [ ] **$httpBackend Setup**: Properly configured\n- [ ] **HTTP Expectations Set**: Before making requests\n- [ ] **Requests Flushed**: `$httpBackend.flush()` called\n- [ ] **Verification Done**: `verifyNoOutstandingExpectation()`\n- [ ] **Error Responses Tested**: 404, 500, timeout scenarios\n- [ ] **Response Data Correct**: Actual data format verified\n- [ ] **Multiple Requests Handled**: Sequential requests tested\n\n## Async Operations\n\n- [ ] **Promises Resolved**: `$rootScope.$apply()` or `flush()`\n- [ ] **Timeouts Handled**: `$timeout.flush()` called\n- [ ] **Callbacks Executed**: Async code runs before assertions\n- [ ] **Error Handling**: Promise rejections tested\n- [ ] **Race Conditions**: Timing issues caught\n- [ ] **Async Completeness**: All async operations finished\n\n## Scope & Events\n\n- [ ] **$scope Created**: Fresh scope per test\n- [ ] **Watchers Tested**: `$scope.$watch()` behavior verified\n- [ ] **Events Emitted**: `$scope.$emit()` called\n- [ ] **Events Broadcast**: `$scope.$broadcast()` verified\n- [ ] **Digest Cycles**: `$rootScope.$apply()` or `$digest()` called\n- [ ] **Scope Cleanup**: Resources cleaned up\n- [ ] **Scope Isolation**: Controller scope is isolated\n\n## Assertions\n\n- [ ] **Assertions Meaningful**: Verify intended behavior\n- [ ] **Type Checks**: `toBeDefined()`, `toBeNull()`\n- [ ] **Value Checks**: `toBe()`, `toEqual()`\n- [ ] **Array/Object Checks**: `toContain()`, deep equality\n- [ ] **Error Checks**: `toThrow()`, error messages\n- [ ] **Spy Checks**: Function calls verified\n- [ ] **Negation Used**: `.not.` when appropriate\n\n## Error Testing\n\n- [ ] **Error Paths Tested**: Happy path AND error cases\n- [ ] **Error Messages Verified**: Clear, helpful messages\n- [ ] **Error Recovery**: Service recovers gracefully\n- [ ] **Edge Cases**: Empty arrays, null values, invalid input\n- [ ] **Boundary Values**: Min/max values tested\n- [ ] **Invalid Data**: Type mismatches handled\n- [ ] **Exception Handling**: Uncaught exceptions prevented\n\n## Coverage & Quality\n\n- [ ] **Coverage 80%+**: Adequate code coverage\n- [ ] **Line Coverage Good**: Most lines executed\n- [ ] **Branch Coverage**: Both if/else branches tested\n- [ ] **Function Coverage**: All functions called\n- [ ] **No Dead Code**: All code is tested\n- [ ] **Conditional Branches**: All conditions tested\n- [ ] **Error Branches**: Error handling tested\n\n## Maintainability\n\n- [ ] **Fixtures Used**: Test data centralized\n- [ ] **Helpers Created**: Common operations extracted\n- [ ] **DRY Principle**: No code duplication\n- [ ] **Clear Variable Names**: Intent obvious\n- [ ] **Documented**: Complex logic explained\n- [ ] **Constants Used**: Magic numbers avoided\n- [ ] **Consistent Style**: Team conventions followed\n\n## Performance\n\n- [ ] **Tests Run Fast**: Under 100ms per test\n- [ ] **No Unnecessary Mocks**: Only mock when needed\n- [ ] **Efficient Assertions**: Quick validations\n- [ ] **Async Operations**: Properly handled\n- [ ] **Database Mocked**: No real DB access\n- [ ] **External APIs Mocked**: No real API calls\n- [ ] **Resource Cleanup**: No memory leaks\n\n## Integration Testing\n\n- [ ] **Component Interaction**: Multiple components tested\n- [ ] **Data Flow**: Data flows correctly through system\n- [ ] **Event Propagation**: Events propagate correctly\n- [ ] **Service Integration**: Services work together\n- [ ] **Router Integration**: Navigation works\n- [ ] **HTTP Integration**: Real HTTP scenarios tested\n- [ ] **End-to-End**: Full workflow tested\n\n## Debugging\n\n- [ ] **Errors Understandable**: Clear error messages\n- [ ] **Test Names Descriptive**: Easy to find failing test\n- [ ] **Assertions Clear**: Obvious what failed\n- [ ] **No Silent Failures**: Failures are visible\n- [ ] **Logging Minimal**: Debug logs removed\n- [ ] **Focus/Skip Used**: For debugging specific tests\n- [ ] **Browser DevTools**: Tests debuggable\n\n## CI/CD Integration\n\n- [ ] **Tests Run in CI**: Automated test execution\n- [ ] **Coverage Reported**: Coverage data tracked\n- [ ] **Failures Caught**: CI catches test failures\n- [ ] **Build Blocked**: Failed tests block merge\n- [ ] **Reports Generated**: HTML/coverage reports\n- [ ] **Artifacts Saved**: Test results archived\n- [ ] **Notifications Sent**: Team notified of failures\n\n## Documentation\n\n- [ ] **README Updated**: Testing instructions included\n- [ ] **Test Examples**: Sample tests documented\n- [ ] **Setup Documented**: How to run tests explained\n- [ ] **Coverage Report**: Coverage goals documented\n- [ ] **Common Issues**: Troubleshooting guide provided\n- [ ] **Best Practices**: Team guidelines documented\n- [ ] **Tools Listed**: Testing tools and versions listed\n\n## Scoring\n\nCalculate your test quality score:\n\n| Item | Yes(1) | Partial(0.5) | No(0) |\n|------|--------|--------------|-------|\n| Pre-test planning (7 items) | ? | ? | ? |\n| Test organization (7 items) | ? | ? | ? |\n| Test structure (7 items) | ? | ? | ? |\n| Mocking (7 items) | ? | ? | ? |\n| HTTP testing (7 items) | ? | ? | ? |\n| Async operations (7 items) | ? | ? | ? |\n| Scope & events (7 items) | ? | ? | ? |\n| Assertions (7 items) | ? | ? | ? |\n| Error testing (7 items) | ? | ? | ? |\n| Coverage (7 items) | ? | ? | ? |\n\n**Score Calculation**: (Total Points / 70) × 100\n\n- **90-100**: Excellent test quality ✓\n- **75-89**: Good test quality - improve coverage\n- **60-74**: Fair quality - address gaps\n- **\u003c60**: Poor quality - significant improvements needed\n\n## Quick Checklist for Code Review\n\nBefore committing tests:\n\n- [ ] Tests run locally without errors\n- [ ] Coverage meets 80% target\n- [ ] All edge cases tested\n- [ ] No flaky tests\n- [ ] No console.log statements\n- [ ] No pending/skipped tests\n- [ ] Naming follows conventions\n- [ ] All assertions meaningful\n- [ ] Error scenarios tested\n- [ ] Mocks are realistic\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7641,"content_sha256":"6b3ac7ff64a08db6e98eb9776858b129b8127efc3ae051d19fc7d7c4eedb5150"},{"filename":"resources/coverage-analysis-guide.md","content":"# Code Coverage Analysis Guide\n\n## Understanding Code Coverage\n\nCode coverage measures what percentage of your code is executed by your tests. It's an important metric for test quality.\n\n### Coverage Types\n\n| Type | Measures | Target |\n|------|----------|--------|\n| **Line Coverage** | Percentage of lines executed | 80%+ |\n| **Branch Coverage** | Percentage of conditional branches executed | 75%+ |\n| **Function Coverage** | Percentage of functions called | 80%+ |\n| **Statement Coverage** | Percentage of statements executed | 80%+ |\n\n## Using Istanbul/NYC for Coverage\n\n### Installation\n\n```bash\nnpm install --save-dev istanbul nyc\n```\n\n### Configuration\n\nIn `package.json`:\n\n```json\n{\n \"scripts\": {\n \"test\": \"karma start\",\n \"test:coverage\": \"karma start --coverage\"\n }\n}\n```\n\nIn `karma.conf.js`:\n\n```javascript\ncoverageReporter: {\n dir: require('path').join(__dirname, 'coverage'),\n subdir: '.',\n reporters: [\n { type: 'html' },\n { type: 'text-summary' },\n { type: 'lcov' }\n ],\n check: {\n global: {\n statements: 80,\n branches: 75,\n functions: 80,\n lines: 80\n }\n }\n}\n```\n\n### Running Coverage\n\n```bash\nnpm run test:coverage\n```\n\n## Interpreting Coverage Reports\n\n### HTML Report\n\nThe coverage report generates an `index.html` file:\n\n```\ncoverage/\n├── index.html (Main report)\n├── base/\n│ ├── src/\n│ │ ├── services/\n│ │ │ ├── UserService.js (Coverage details)\n│ │ │ └── user.service.html\n│ │ └── controllers/\n│ │ ├── UserController.js\n│ │ └── user.controller.html\n```\n\n### Reading Coverage Numbers\n\n```\nStatements : 85.5% ( 320/375 ) ← 320 of 375 statements executed\nBranches : 78.2% ( 86/110 ) ← 78% of conditional branches covered\nFunctions : 82.1% ( 32/39 ) ← 82% of functions called\nLines : 86.3% ( 298/345 ) ← 86% of lines executed\n```\n\n### Visual Indicators\n\n```javascript\n// ✓ Line executed (green)\nif (user) {\n console.log(user.name);\n}\n\n// ✗ Line not executed (red)\nif (admin) {\n deleteAllData(); // This code path not tested\n}\n\n// ~ Branch not fully covered (yellow)\nif (user && user.active) { // Some branches missing\n doSomething();\n}\n```\n\n## Improving Coverage\n\n### Finding Coverage Gaps\n\n1. Open `coverage/index.html` in browser\n2. Look for red (uncovered) lines\n3. Check yellow (partially covered) branches\n4. Identify missing test scenarios\n\n### Common Coverage Gaps\n\n#### Error Handling\n\n```javascript\n// BAD: No error handling test\nservice.getUser = function(id) {\n return $http.get('/api/users/' + id);\n};\n\n// GOOD: Test error path\nit('should handle 404 errors', function() {\n $httpBackend.expectGET('/api/users/999').respond(404);\n \n var error;\n service.getUser(999).then(null, function(err) {\n error = err;\n });\n \n $httpBackend.flush();\n expect(error).toBeDefined();\n});\n```\n\n#### Conditional Branches\n\n```javascript\n// BAD: Only testing happy path\ncontroller.submit = function() {\n if (form.$valid) {\n service.save(form.data);\n }\n};\n\nit('should save when form is valid', function() {\n $scope.form = { $valid: true, data: {} };\n $scope.submit();\n // Only tests valid branch!\n});\n\n// GOOD: Test both branches\nit('should not save when form is invalid', function() {\n $scope.form = { $valid: false };\n $scope.submit();\n expect(service.save).not.toHaveBeenCalled();\n});\n```\n\n#### Edge Cases\n\n```javascript\n// BAD: Not testing edge cases\nfunction processUsers(users) {\n return users.map(function(user) {\n return user.name.toUpperCase();\n });\n}\n\n// GOOD: Test edge cases\nit('should handle empty array', function() {\n expect(processUsers([])).toEqual([]);\n});\n\nit('should handle null users', function() {\n expect(function() {\n processUsers([null]);\n }).toThrow();\n});\n\nit('should handle users without names', function() {\n expect(function() {\n processUsers([{}]);\n }).toThrow();\n});\n```\n\n#### Loop Coverage\n\n```javascript\n// BAD: Incomplete loop testing\nfor (var i = 0; i \u003c items.length; i++) {\n if (items[i].active) {\n results.push(items[i]);\n }\n}\n\n// GOOD: Test different loop scenarios\nit('should process all items', function() {\n var items = [\n { active: true },\n { active: false },\n { active: true }\n ];\n \n expect(processItems(items).length).toBe(2);\n});\n```\n\n## Coverage-Driven Testing\n\n### Workflow\n\n1. Run tests with coverage\n2. Identify uncovered code\n3. Write tests for uncovered paths\n4. Re-run coverage\n5. Repeat until target reached\n\n### Example\n\n```javascript\n// 1. Run coverage\nnpm run test:coverage\n// Coverage: 65%\n\n// 2. Add missing tests for error handling\nit('should handle timeouts', function() {\n $timeout(function() {\n expect(true).toBe(true);\n }, 5000);\n $timeout.flush();\n});\n\n// 3. Re-run coverage\nnpm run test:coverage\n// Coverage: 78%\n\n// 4. Continue improving\n```\n\n## Coverage Best Practices\n\n| ✓ Do | ✗ Don't |\n|------|---------|\n| Test error paths | Only test happy paths |\n| Test edge cases | Test only main scenarios |\n| Test boundary values | Ignore edge cases |\n| Test false conditions | Only test true conditions |\n| Test async operations | Skip async tests |\n| Track coverage trends | Ignore coverage metrics |\n\n## Coverage Reporting\n\n### Text Summary\n\n```\nTOTAL: 85.5%\nPASS: ✓ 150 tests passed\nWARNINGS: 5 files below threshold\n```\n\n### CI/CD Integration\n\n#### GitHub Actions\n\n```yaml\n- name: Test Coverage\n run: npm run test:coverage\n\n- name: Upload Coverage\n uses: codecov/codecov-action@v2\n with:\n files: ./coverage/lcov.info\n```\n\n#### Jenkins\n\n```groovy\nstage('Coverage') {\n steps {\n sh 'npm run test:coverage'\n publishHTML([\n reportDir: 'coverage',\n reportFiles: 'index.html',\n reportName: 'Coverage Report'\n ])\n }\n}\n```\n\n## Managing Coverage Thresholds\n\n### Setting Thresholds\n\n```javascript\n// karma.conf.js\ncoverageReporter: {\n check: {\n global: {\n statements: 80,\n branches: 75,\n functions: 80,\n lines: 80\n },\n each: {\n statements: 70,\n branches: 65,\n functions: 70,\n lines: 70\n }\n }\n}\n```\n\n### Excluding Code from Coverage\n\n```javascript\n// Skip coverage for this file\n// istanbul ignore file\n\n// Skip single line\n/* istanbul ignore next */ \nif (process.env.NODE_ENV === 'test') { ... }\n\n// Skip next line\n/* istanbul ignore next */\nconsole.log('Debug info');\n```\n\n## Common Coverage Challenges\n\n### Challenge 1: Circular Dependencies\n\n```javascript\n// Mock circular deps in tests\nbeforeEach(module(function($provide) {\n $provide.value('ServiceA', mockServiceA);\n $provide.value('ServiceB', mockServiceB);\n}));\n```\n\n### Challenge 2: Browser APIs\n\n```javascript\n// Mock browser APIs\nbeforeEach(function() {\n spyOn(localStorage, 'getItem').and.returnValue('value');\n spyOn(window, 'alert');\n});\n```\n\n### Challenge 3: Heavy Setup Code\n\n```javascript\n// Extract setup to helper functions\nfunction setupDatabase() {\n // Complex setup\n}\n\nfunction setupAuth() {\n // Auth setup\n}\n\nbeforeEach(function() {\n setupDatabase();\n setupAuth();\n});\n```\n\n## Coverage Tools Comparison\n\n| Tool | Language | Integration | Report |\n|------|----------|-------------|--------|\n| **Istanbul** | JavaScript | Karma | HTML, LCOV |\n| **NYC** | JavaScript | CLI | HTML, JSON |\n| **Coveralls** | Multi | CI/CD | Web |\n| **Codecov** | Multi | CI/CD | Web |\n\n## References\n\n- [Istanbul Documentation](https://istanbul.js.org/)\n- [NYC Documentation](https://github.com/istanbuljs/nyc)\n- [Code Coverage Wikipedia](https://en.wikipedia.org/wiki/Code_coverage)\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":7624,"content_sha256":"b1db1a5178bebb95af2ef527502a17de41cee634b077e9af456a9741a86eb96f"},{"filename":"resources/http-mocking-guide.md","content":"# HTTP Mocking Guide for AngularJS Testing\n\n## Complete Guide to $httpBackend Mocking\n\n### Overview\n\nThe `$httpBackend` service is a mock HTTP backend for AngularJS testing. It allows you to define expected HTTP requests and specify responses without making actual network calls.\n\n## Basic HTTP Mocking\n\n### Expecting HTTP Requests\n\n```javascript\n// GET request\n$httpBackend.expectGET('/api/users').respond([\n { id: 1, name: 'John' }\n]);\n\n// POST request\n$httpBackend.expectPOST('/api/users', { name: 'Jane' }).respond({\n id: 2,\n name: 'Jane'\n});\n\n// PUT request\n$httpBackend.expectPUT('/api/users/1', { name: 'John Updated' }).respond({\n id: 1,\n name: 'John Updated'\n});\n\n// DELETE request\n$httpBackend.expectDELETE('/api/users/1').respond('');\n\n// PATCH request\n$httpBackend.expectPATCH('/api/users/1', { status: 'active' }).respond({\n id: 1,\n status: 'active'\n});\n```\n\n### Response Formats\n\n```javascript\n// Simple response (status 200)\n$httpBackend.expectGET('/api/users').respond([users]);\n\n// Response with status code\n$httpBackend.expectGET('/api/users').respond(200, [users]);\n\n// Response with status and headers\n$httpBackend.expectGET('/api/users').respond(200, [users], {\n 'X-Custom-Header': 'value'\n});\n\n// Error response\n$httpBackend.expectGET('/api/users').respond(500, 'Server error');\n$httpBackend.expectGET('/api/users').respond(404, 'Not found');\n$httpBackend.expectGET('/api/users').respond(400, { error: 'Bad request' });\n```\n\n## URL Pattern Matching\n\n### Exact URL Match\n\n```javascript\n$httpBackend.expectGET('/api/users').respond([]);\n```\n\n### Regex Pattern Matching\n\n```javascript\n// Match any user ID\n$httpBackend.expectGET(/^\\/api\\/users\\/\\d+$/).respond({\n id: 1,\n name: 'John'\n});\n\n// Match multiple endpoints\n$httpBackend.expectGET(/^\\/api\\/(users|posts)$/).respond([]);\n\n// Match with query parameters\n$httpBackend.expectGET(/^\\/api\\/users\\?.*$/).respond([]);\n```\n\n### Custom Matching Functions\n\n```javascript\n$httpBackend.expectGET(function(url) {\n return url.indexOf('/api/users') !== -1;\n}).respond([]);\n```\n\n## Request Data Matching\n\n### Exact Data Match\n\n```javascript\nvar userData = { name: 'John', email: '[email protected]' };\n\n$httpBackend.expectPOST('/api/users', userData).respond({\n id: 1,\n ...userData\n});\n```\n\n### Partial Data Match\n\n```javascript\n// Match POST with any data\n$httpBackend.expectPOST('/api/users', function(data) {\n // Return true if data matches your criteria\n return data.name && data.email;\n}).respond({ id: 1 });\n```\n\n### Function Matching\n\n```javascript\n$httpBackend.expectPOST('/api/users', function(data) {\n var parsed = JSON.parse(data);\n return parsed.name === 'John';\n}).respond(201, { id: 1, name: 'John' });\n```\n\n## Multiple Requests\n\n### Sequential Requests\n\n```javascript\n// Each expectation is matched in order\n$httpBackend.expectGET('/api/users').respond([user1]);\n$httpBackend.expectGET('/api/users').respond([user2]);\n\nservice.getUsers(); // First call matches first expectation\nservice.getUsers(); // Second call matches second expectation\n\n$httpBackend.flush();\n```\n\n### Multiple Times\n\n```javascript\n// Allow same endpoint to be called multiple times\n$httpBackend.expectGET('/api/users').respond([users]);\n$httpBackend.expectGET('/api/users').respond([users]);\n\n// Or use whenGET for unlimited calls\n$httpBackend.whenGET('/api/users').respond([users]);\n\nservice.getUsers();\nservice.getUsers();\nservice.getUsers();\n\n$httpBackend.flush();\n```\n\n## Expectations vs When\n\n### Using expectGET/expectPOST\n\n```javascript\n// Must be called exactly as specified\n$httpBackend.expectGET('/api/users').respond([]);\n\n// Test will fail if not called\n$httpBackend.verifyNoOutstandingExpectation();\n```\n\n### Using whenGET/whenPOST\n\n```javascript\n// Can be called any number of times\n$httpBackend.whenGET('/api/users').respond([]);\n\n// Will not fail if not called\n// Useful for optional requests\n```\n\n### When to Use Each\n\n```javascript\n// expectGET: Verify specific requests are made\n$httpBackend.expectGET('/api/users').respond([users]);\n$httpBackend.expectGET('/api/users/1').respond(user1);\n\n// whenGET: Define default responses for any request\n$httpBackend.whenGET(/^\\/api\\/.*/).respond([]);\n$httpBackend.whenPOST('/api/data', angular.identity).respond(201);\n```\n\n## Complete HTTP Mocking Example\n\n```javascript\ndescribe('UserService with HTTP mocking', function() {\n var userService, $httpBackend;\n\n beforeEach(module('myApp'));\n\n beforeEach(inject(function(_UserService_, _$httpBackend_) {\n userService = _UserService_;\n $httpBackend = _$httpBackend_;\n }));\n\n afterEach(function() {\n // Verify all expected requests were made\n $httpBackend.verifyNoOutstandingExpectation();\n // Verify no unexpected requests were made\n $httpBackend.verifyNoOutstandingRequest();\n });\n\n describe('GET requests', function() {\n it('should fetch all users', function() {\n var expectedUsers = [\n { id: 1, name: 'John' },\n { id: 2, name: 'Jane' }\n ];\n\n $httpBackend.expectGET('/api/users').respond(expectedUsers);\n\n var result;\n userService.getUsers().then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result).toEqual(expectedUsers);\n });\n\n it('should fetch single user by ID', function() {\n var expectedUser = { id: 1, name: 'John' };\n\n // Use regex for dynamic IDs\n $httpBackend.expectGET(/^\\/api\\/users\\/\\d+$/).respond(expectedUser);\n\n var result;\n userService.getUser(1).then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result).toEqual(expectedUser);\n });\n\n it('should handle query parameters', function() {\n var expectedUsers = [{ id: 1, name: 'John', active: true }];\n\n $httpBackend.expectGET('/api/users?active=true').respond(expectedUsers);\n\n var result;\n userService.getActiveUsers().then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result).toEqual(expectedUsers);\n });\n });\n\n describe('POST requests', function() {\n it('should create new user with correct data', function() {\n var newUserData = { name: 'Bob', email: '[email protected]' };\n var createdUser = { id: 3, ...newUserData };\n\n $httpBackend.expectPOST('/api/users', newUserData).respond(201, createdUser);\n\n var result;\n userService.createUser(newUserData).then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result.id).toBe(3);\n });\n\n it('should create user with partial data matching', function() {\n $httpBackend.expectPOST('/api/users', function(data) {\n var user = JSON.parse(data);\n return user.name === 'Bob';\n }).respond(201, { id: 3, name: 'Bob' });\n\n var result;\n userService.createUser({ name: 'Bob', email: '[email protected]' })\n .then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result.name).toBe('Bob');\n });\n });\n\n describe('PUT requests', function() {\n it('should update user with correct data', function() {\n var updates = { name: 'John Updated' };\n var updatedUser = { id: 1, name: 'John Updated' };\n\n $httpBackend.expectPUT('/api/users/1', updates).respond(updatedUser);\n\n var result;\n userService.updateUser(1, updates).then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result.name).toBe('John Updated');\n });\n });\n\n describe('DELETE requests', function() {\n it('should delete user', function() {\n $httpBackend.expectDELETE('/api/users/1').respond(204, '');\n\n var result;\n userService.deleteUser(1).then(function(data) {\n result = data;\n });\n\n $httpBackend.flush();\n\n expect(result).toBeDefined();\n });\n });\n\n describe('Error handling', function() {\n it('should handle 404 errors', function() {\n $httpBackend.expectGET('/api/users/999').respond(404, 'Not found');\n\n var result;\n var error;\n\n userService.getUser(999)\n .then(function(data) {\n result = data;\n }, function(err) {\n error = err;\n });\n\n $httpBackend.flush();\n\n expect(error).toBeDefined();\n });\n\n it('should handle 500 server errors', function() {\n $httpBackend.expectGET('/api/users').respond(500, 'Server error');\n\n var error;\n\n userService.getUsers()\n .then(null, function(err) {\n error = err;\n });\n\n $httpBackend.flush();\n\n expect(error).toBeDefined();\n });\n\n it('should handle network timeouts', function() {\n var deferred = $q.defer();\n $httpBackend.expectGET('/api/users').respond(function() {\n return [0, null]; // 0 status code = no response\n });\n\n var error;\n userService.getUsers().then(null, function(err) {\n error = err;\n });\n\n $httpBackend.flush();\n\n expect(error).toBeDefined();\n });\n });\n\n describe('Multiple requests', function() {\n it('should handle sequential requests', function() {\n var users = [{ id: 1, name: 'John' }];\n var newUser = { id: 2, name: 'Jane' };\n\n $httpBackend.expectGET('/api/users').respond(users);\n $httpBackend.expectPOST('/api/users').respond(newUser);\n\n var getResult, postResult;\n\n userService.getUsers().then(function(data) {\n getResult = data;\n });\n\n userService.createUser(newUser).then(function(data) {\n postResult = data;\n });\n\n $httpBackend.flush();\n\n expect(getResult).toEqual(users);\n expect(postResult).toEqual(newUser);\n });\n\n it('should handle multiple calls to same endpoint', function() {\n $httpBackend.whenGET('/api/data').respond([]);\n\n service.getData();\n service.getData();\n service.getData();\n\n $httpBackend.flush();\n // No verification error because whenGET allows multiple calls\n });\n });\n});\n```\n\n## Tips & Best Practices\n\n1. **Always use whenGET/whenPOST for optional endpoints** that might not be called\n2. **Use regex matching for dynamic IDs** to reduce brittleness\n3. **Verify no outstanding requests** with `$httpBackend.verifyNoOutstandingRequest()`\n4. **Match request data carefully** to catch wrong API usage\n5. **Test error responses** thoroughly\n6. **Flush requests explicitly** with `$httpBackend.flush()`\n7. **Clean up with afterEach** to catch leftover requests\n\n## Common Pitfalls\n\n```javascript\n// ❌ Forgetting to flush requests\nit('should work', function() {\n $httpBackend.expectGET('/api/users').respond([]);\n service.getUsers();\n // Forgot $httpBackend.flush()!\n});\n\n// ✅ Always flush\nit('should work', function() {\n $httpBackend.expectGET('/api/users').respond([]);\n service.getUsers();\n $httpBackend.flush();\n});\n\n// ❌ Not verifying expectations\nit('should work', function() {\n $httpBackend.expectGET('/api/users').respond([]);\n // Never called getUsers()\n});\n\n// ✅ Verify in afterEach\nafterEach(function() {\n $httpBackend.verifyNoOutstandingExpectation();\n});\n```\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":11105,"content_sha256":"d1a7e204db7b1587fbf5e055d8725350019bde44977f5d14de06e49b8dd6b8e5"},{"filename":"resources/jest-migration-guide.md","content":"# Jest Migration Guide for AngularJS\n\n## Overview\n\nJest is a modern testing framework from Facebook with powerful capabilities including built-in mocking, snapshot testing, and code coverage. This guide explains how to migrate AngularJS tests from Jasmine to Jest or run both frameworks in parallel.\n\n## When to Use Jest\n\n### Jest is ideal for:\n- **New projects**: Modern TypeScript/ES6 codebases\n- **React/Vue testing**: Native support for modern frameworks\n- **Snapshot testing**: Visual regression testing\n- **Fast test execution**: Parallel test runner with isolated environments\n- **Zero configuration**: Sensible defaults out of the box\n- **Built-in coverage**: Coverage reporting built-in\n\n### Jasmine is ideal for:\n- **Legacy AngularJS 1.x projects**: Extensive AngularJS integration\n- **Browser testing**: Tests run in real browsers via Karma\n- **CI/CD pipelines**: Mature Karma integration\n- **Team familiarity**: Existing codebases and team expertise\n\n## Key Differences Between Jasmine and Jest\n\n| Feature | Jasmine | Jest |\n|---------|---------|------|\n| **Test Runner** | Karma (browser-based) | Jest (Node.js-based) |\n| **Module System** | CommonJS/globals | CommonJS/ES6 modules |\n| **Syntax** | `function() { }` | Arrow functions `() => { }` |\n| **Test Functions** | `it()` | `test()` or `it()` |\n| **Mocking** | `spyOn()`, manual mocks | `jest.mock()`, auto-mocking |\n| **Assertions** | Same `expect()` API | Same `expect()` API |\n| **Setup/Teardown** | `beforeEach/afterEach` | `beforeEach/afterEach` |\n| **Coverage** | Istanbul/nyc (plugin) | Built-in, fast |\n| **Snapshots** | Not supported | Built-in `toMatchSnapshot()` |\n| **Isolation** | Shared environment | Isolated per test file |\n\n## Installation\n\n### Option 1: Jest Only (New Projects)\n\n```bash\nnpm install --save-dev jest jest-preset-angular\nnpm install --save-dev ts-jest @types/jest # For TypeScript\n```\n\n### Option 2: Jasmine + Jest (Gradual Migration)\n\n```bash\n# Keep existing Jasmine/Karma setup\nnpm install --save-dev jasmine karma karma-jasmine\n\n# Add Jest for new tests\nnpm install --save-dev jest jest-preset-angular\n```\n\n## Configuration\n\n### Jest Configuration (jest.config.js)\n\n```javascript\nmodule.exports = {\n preset: 'jest-preset-angular',\n setupFilesAfterEnv: ['\u003crootDir>/setup-jest.ts'],\n testPathIgnorePatterns: ['/node_modules/', '/dist/'],\n globals: {\n 'ts-jest': {\n tsconfig: '\u003crootDir>/tsconfig.spec.json',\n stringifyContentPathRegex: '\\\\.(html|svg)

AngularJS Unit Testing Skill Overview This skill specializes in writing, refactoring, and maintaining high-quality unit tests for AngularJS (1.x) applications. It covers controllers, services, filters, directives, HTTP mocking, promises, and dependency injection — everything you need to keep an AngularJS codebase well-tested and reliable. Note : AngularJS reached end-of-life in December 2021. It receives only critical security fixes. New projects should use Angular 19+. For teams maintaining AngularJS codebases, this skill provides the latest testing patterns, tooling, and migration guidance.…

,\n },\n },\n collectCoverage: true,\n collectCoverageFrom: [\n 'src/**/*.ts',\n '!src/**/*.spec.ts',\n '!src/main.ts',\n ],\n};\n```\n\n### setup-jest.ts\n\n```typescript\nimport 'jest-preset-angular/setup-jest';\n\nObject.defineProperty(window, 'CSS', {value: null});\nObject.defineProperty(window, 'getComputedStyle', {\n value: () => ({display: 'none'})\n});\n```\n\n## Migration Patterns\n\n### Pattern 1: Converting Jasmine to Jest\n\n```javascript\n// BEFORE (Jasmine)\ndescribe('UserService', function() {\n var userService, $httpBackend;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_UserService_, _$httpBackend_) {\n userService = _UserService_;\n $httpBackend = _$httpBackend_;\n }));\n\n it('should fetch users', function() {\n $httpBackend.expectGET('/api/users').respond([]);\n userService.getUsers();\n $httpBackend.flush();\n });\n});\n\n// AFTER (Jest)\ndescribe('UserService', () => {\n let userService;\n\n beforeEach(() => {\n userService = require('./UserService').default;\n });\n\n test('should fetch users', async () => {\n global.fetch = jest.fn().mockResolvedValueOnce({\n ok: true,\n json: async () => []\n });\n\n const users = await userService.getUsers();\n expect(users).toBeDefined();\n });\n});\n```\n\n### Pattern 2: Mocking Services\n\n```javascript\n// Jasmine Spies\nspyOn(userService, 'getUser').and.returnValue(\n Promise.resolve({ id: 1, name: 'John' })\n);\n\n// Jest Mocks\nuserService.getUser = jest.fn()\n .mockResolvedValue({ id: 1, name: 'John' });\n```\n\n## Running Tests\n\n### Jest Commands\n\n```bash\nnpm test # Run all tests\nnpm test -- --watch # Watch mode\nnpm test -- --coverage # With coverage\nnpm test -- --updateSnapshot # Update snapshots\nnpm test -- --ci # CI mode\n```\n\n### Karma Commands (Jasmine)\n\n```bash\nnpm test # Run tests\nnpm run test:watch # Watch mode\nnpm run test:coverage # With coverage\n```\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4453,"content_sha256":"496053067d906f630d1f5e9693409c26603f90c71c1937baad062360391137df"},{"filename":"resources/mock-helpers-guide.md","content":"# Mock Helpers and Spy Techniques Guide\n\n## Jasmine Spies\n\n### Creating Spies\n\n#### Spy on Existing Functions\n\n```javascript\ndescribe('Spying on functions', function() {\n var obj;\n\n beforeEach(function() {\n obj = {\n getValue: function() {\n return 42;\n }\n };\n });\n\n it('should track spy calls', function() {\n spyOn(obj, 'getValue');\n \n obj.getValue();\n \n expect(obj.getValue).toHaveBeenCalled();\n });\n});\n```\n\n#### Return Values\n\n```javascript\nit('should return custom value', function() {\n spyOn(obj, 'getValue').and.returnValue(100);\n \n expect(obj.getValue()).toBe(100);\n expect(obj.getValue).toHaveBeenCalled();\n});\n```\n\n#### Call Through\n\n```javascript\nit('should call original and track calls', function() {\n spyOn(obj, 'getValue').and.callThrough();\n \n var result = obj.getValue();\n \n expect(result).toBe(42); // Original value\n expect(obj.getValue).toHaveBeenCalled();\n});\n```\n\n#### Custom Implementation\n\n```javascript\nit('should use custom implementation', function() {\n spyOn(obj, 'getValue').and.callFake(function() {\n return this.value || 99;\n });\n \n expect(obj.getValue()).toBe(99);\n expect(obj.getValue).toHaveBeenCalled();\n});\n```\n\n#### Throw Errors\n\n```javascript\nit('should throw custom error', function() {\n spyOn(obj, 'getValue').and.throwError('Custom error');\n \n expect(function() {\n obj.getValue();\n }).toThrowError('Custom error');\n});\n```\n\n### Standalone Spies\n\n```javascript\ndescribe('Standalone spies', function() {\n it('should create spy without object', function() {\n var spy = jasmine.createSpy('myspy').and.returnValue('value');\n \n spy(1, 2);\n \n expect(spy).toHaveBeenCalled();\n expect(spy).toHaveBeenCalledWith(1, 2);\n });\n\n it('should create spy object', function() {\n var mockObj = jasmine.createSpyObj('mockObj', [\n 'method1',\n 'method2',\n 'method3'\n ]);\n \n mockObj.method1('arg');\n \n expect(mockObj.method1).toHaveBeenCalledWith('arg');\n expect(mockObj.method2).not.toHaveBeenCalled();\n });\n});\n```\n\n## Spy Matchers\n\n```javascript\ndescribe('Spy matchers', function() {\n var spy;\n\n beforeEach(function() {\n spy = jasmine.createSpy('spy');\n });\n\n it('should verify spy was called', function() {\n spy();\n expect(spy).toHaveBeenCalled();\n });\n\n it('should verify spy called with specific arguments', function() {\n spy('arg1', 'arg2');\n expect(spy).toHaveBeenCalledWith('arg1', 'arg2');\n });\n\n it('should verify call count', function() {\n spy();\n spy();\n spy();\n expect(spy).toHaveBeenCalledTimes(3);\n });\n\n it('should access call information', function() {\n spy('a', 'b');\n spy('c', 'd');\n \n expect(spy.calls.count()).toBe(2);\n expect(spy.calls.argsFor(0)).toEqual(['a', 'b']);\n expect(spy.calls.argsFor(1)).toEqual(['c', 'd']);\n expect(spy.calls.first().args).toEqual(['a', 'b']);\n expect(spy.calls.mostRecent().args).toEqual(['c', 'd']);\n });\n\n it('should reset spy', function() {\n spy();\n expect(spy).toHaveBeenCalled();\n \n spy.calls.reset();\n expect(spy).not.toHaveBeenCalled();\n });\n});\n```\n\n## Service Mocking Patterns\n\n### Pattern 1: Using $provide.value\n\n```javascript\ndescribe('Service mocking with $provide.value', function() {\n var mockUserService = {\n getUser: jasmine.createSpy('getUser').and.returnValue({\n id: 1,\n name: 'John'\n }),\n updateUser: jasmine.createSpy('updateUser').and.returnValue(true)\n };\n\n beforeEach(module('myApp'));\n beforeEach(module(function($provide) {\n $provide.value('UserService', mockUserService);\n }));\n\n it('should use mock service', inject(function(UserService) {\n expect(UserService).toBe(mockUserService);\n expect(UserService.getUser()).toEqual({ id: 1, name: 'John' });\n }));\n});\n```\n\n### Pattern 2: Using $provide.factory\n\n```javascript\ndescribe('Service mocking with $provide.factory', function() {\n beforeEach(module('myApp'));\n beforeEach(module(function($provide) {\n $provide.factory('UserService', function() {\n return {\n getUser: jasmine.createSpy('getUser').and.returnValue({\n id: 1,\n name: 'John'\n })\n };\n });\n }));\n\n it('should use mocked factory', inject(function(UserService) {\n var user = UserService.getUser();\n expect(user.name).toBe('John');\n }));\n});\n```\n\n### Pattern 3: Partial Mocking\n\n```javascript\ndescribe('Partial service mocking', function() {\n var realService, mockService;\n\n beforeEach(module('myApp'));\n beforeEach(module(function($provide) {\n mockService = {\n // Mock only specific methods\n getUser: jasmine.createSpy('getUser').and.returnValue({\n id: 1,\n name: 'Mock User'\n }),\n // Let other methods use real implementation\n validateUser: jasmine.createSpy('validateUser').and.callFake(\n function(user) {\n return user && user.name;\n }\n )\n };\n\n $provide.value('UserService', mockService);\n }));\n\n it('should use partial mock', inject(function(UserService) {\n expect(UserService.getUser()).toEqual({ id: 1, name: 'Mock User' });\n expect(UserService.validateUser({ name: 'John' })).toBe(true);\n }));\n});\n```\n\n## HTTP Interceptor Mocking\n\n```javascript\ndescribe('HTTP interceptor mocking', function() {\n var $httpBackend;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_$httpBackend_) {\n $httpBackend = _$httpBackend_;\n }));\n\n afterEach(function() {\n $httpBackend.verifyNoOutstandingExpectation();\n });\n\n it('should add authorization header', inject(function($http) {\n $httpBackend.expectGET('/api/users', function(headers) {\n return headers['Authorization'] === 'Bearer token123';\n }).respond([]);\n\n $http.get('/api/users');\n $httpBackend.flush();\n }));\n});\n```\n\n## Mock State Management\n\n```javascript\ndescribe('Mock with state', function() {\n var mockUserService;\n\n beforeEach(function() {\n mockUserService = {\n users: [],\n \n addUser: jasmine.createSpy('addUser').and.callFake(function(user) {\n this.users.push(user);\n return user;\n }),\n \n getUsers: jasmine.createSpy('getUsers').and.callFake(function() {\n return this.users;\n }),\n \n clear: jasmine.createSpy('clear').and.callFake(function() {\n this.users = [];\n })\n };\n });\n\n beforeEach(module('myApp'));\n beforeEach(module(function($provide) {\n $provide.value('UserService', mockUserService);\n }));\n\n it('should manage state in mock', inject(function(UserService) {\n UserService.addUser({ id: 1, name: 'John' });\n UserService.addUser({ id: 2, name: 'Jane' });\n \n expect(UserService.getUsers().length).toBe(2);\n \n UserService.clear();\n expect(UserService.getUsers().length).toBe(0);\n }));\n});\n```\n\n## Common Mock Objects\n\n### Mock Promise\n\n```javascript\ndescribe('Mock promises', function() {\n var $q, $rootScope;\n\n beforeEach(inject(function(_$q_, _$rootScope_) {\n $q = _$q_;\n $rootScope = _$rootScope_;\n }));\n\n it('should create mock resolved promise', function() {\n var mockPromise = $q.when({ id: 1, name: 'John' });\n var result;\n\n mockPromise.then(function(data) {\n result = data;\n });\n\n $rootScope.$apply();\n\n expect(result).toEqual({ id: 1, name: 'John' });\n });\n\n it('should create mock rejected promise', function() {\n var mockPromise = $q.reject('Error message');\n var error;\n\n mockPromise.then(null, function(err) {\n error = err;\n });\n\n $rootScope.$apply();\n\n expect(error).toBe('Error message');\n });\n});\n```\n\n### Mock $scope\n\n```javascript\ndescribe('Mock $scope', function() {\n var mockScope;\n\n beforeEach(function() {\n mockScope = {\n users: [],\n selectedUser: null,\n $watch: jasmine.createSpy('$watch'),\n $apply: jasmine.createSpy('$apply'),\n $emit: jasmine.createSpy('$emit'),\n $broadcast: jasmine.createSpy('$broadcast')\n };\n });\n\n it('should use mock scope', function() {\n mockScope.users.push({ id: 1, name: 'John' });\n \n expect(mockScope.users.length).toBe(1);\n expect(mockScope.$emit).not.toHaveBeenCalled();\n \n mockScope.$emit('userSelected', mockScope.users[0]);\n expect(mockScope.$emit).toHaveBeenCalledWith('userSelected', mockScope.users[0]);\n });\n});\n```\n\n## Debugging with Spies\n\n```javascript\ndescribe('Debugging spies', function() {\n var spy;\n\n beforeEach(function() {\n spy = jasmine.createSpy('spy');\n });\n\n it('should debug spy calls', function() {\n spy('arg1', 'arg2');\n spy('arg3', 'arg4');\n\n // Print all calls\n console.log('Call count:', spy.calls.count());\n console.log('All calls:', spy.calls.all());\n console.log('First call:', spy.calls.first());\n console.log('Most recent:', spy.calls.mostRecent());\n\n // Check specific call\n if (spy.calls.count() > 0) {\n var args = spy.calls.argsFor(0);\n console.log('First call args:', args);\n }\n\n expect(spy).toHaveBeenCalled();\n });\n});\n```\n\n## Best Practices\n\n1. **Mock external dependencies**: HTTP, external APIs, browser APIs\n2. **Keep mocks realistic**: Mock should behave like real service\n3. **Use spyOn for tracking**: Verify services are called correctly\n4. **Clear mocks between tests**: Prevent state pollution\n5. **Document complex mocks**: Explain mock behavior\n6. **Test with both mocks and real services**: Catch integration issues\n7. **Use jasmine.createSpyObj for multiple methods**: Reduces boilerplate\n\n## Common Patterns\n\n```javascript\n// Pattern 1: Simple mock\nvar mockService = {\n getData: jasmine.createSpy('getData').and.returnValue([])\n};\n\n// Pattern 2: State-aware mock\nvar mockService = {\n data: [],\n getData: jasmine.createSpy('getData').and.callFake(function() {\n return this.data;\n })\n};\n\n// Pattern 3: Multi-method mock\nvar mockService = jasmine.createSpyObj('MockService', [\n 'getData',\n 'saveData',\n 'deleteData'\n]);\n\n// Pattern 4: Partial mock\nvar mockService = {\n ...realServiceInstance,\n expensiveMethod: jasmine.createSpy('expensiveMethod')\n};\n```\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10089,"content_sha256":"1823eab7dff64183e0abce4481b2c634807ba1fe00e5e5584187ab5cb43b0495"},{"filename":"resources/testing-patterns-guide.md","content":"# AngularJS Testing Patterns Guide\n\n**Tested with**: Jasmine (recommended) and Jest\n\nThese patterns work with both Jasmine and Jest testing frameworks. Code examples show Jasmine syntax, but Jest equivalents are noted where they differ.\n\n## Recommended Testing Patterns\n\n### 1. AAA Pattern (Arrange-Act-Assert)\n\n**Pattern**: Every test consists of three distinct phases\n\n```javascript\n// JASMINE\ndescribe('UserService', function() {\n var userService;\n\n beforeEach(inject(function(_UserService_) {\n userService = _UserService_;\n }));\n\n it('should calculate total age correctly', function() {\n // ARRANGE: Set up test data\n var users = [\n { name: 'John', age: 30 },\n { name: 'Jane', age: 25 }\n ];\n\n // ACT: Call the method being tested\n var total = userService.calculateTotalAge(users);\n\n // ASSERT: Verify the result\n expect(total).toBe(55);\n });\n});\n```\n\n**Benefits**:\n- Clear structure makes tests easy to read\n- Each phase has a specific purpose\n- Easier to debug failing tests\n\n---\n\n### 2. Given-When-Then Pattern\n\n**Pattern**: BDD style describing setup, action, and result\n\n```javascript\ndescribe('UserController', function() {\n var $scope, controller;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('UserController', {\n $scope: $scope\n });\n }));\n\n // GIVEN: The controller is initialized\n describe('GIVEN user list is populated', function() {\n // WHEN: User clicks delete button\n it('WHEN user deletes an item THEN it removes from list', function() {\n // Setup: GIVEN\n $scope.users = [\n { id: 1, name: 'John' },\n { id: 2, name: 'Jane' }\n ];\n\n // Action: WHEN\n $scope.deleteUser(1);\n\n // Verification: THEN\n expect($scope.users.length).toBe(1);\n expect($scope.users[0].id).toBe(2);\n });\n });\n});\n```\n\n**Benefits**:\n- Natural language describes test scenarios\n- Non-technical stakeholders can understand tests\n- Clear cause-effect relationships\n\n---\n\n### 3. Test Fixtures Pattern\n\n**Pattern**: Centralize reusable test data and setup\n\n```javascript\ndescribe('UserService', function() {\n var userService, mockData;\n\n // Define reusable test data\n beforeEach(function() {\n mockData = {\n validUser: {\n id: 1,\n name: 'John Doe',\n email: '[email protected]',\n age: 30\n },\n invalidUser: {\n id: null,\n name: '',\n email: 'invalid',\n age: -5\n },\n users: [\n { id: 1, name: 'John', active: true },\n { id: 2, name: 'Jane', active: false },\n { id: 3, name: 'Bob', active: true }\n ]\n };\n });\n\n beforeEach(inject(function(_UserService_) {\n userService = _UserService_;\n }));\n\n it('should validate user with valid data', function() {\n var result = userService.validate(mockData.validUser);\n expect(result.isValid).toBe(true);\n });\n\n it('should reject user with invalid data', function() {\n var result = userService.validate(mockData.invalidUser);\n expect(result.isValid).toBe(false);\n });\n\n it('should filter active users', function() {\n var active = userService.filterActive(mockData.users);\n expect(active.length).toBe(2);\n });\n});\n```\n\n**Benefits**:\n- Reduces test duplication\n- Ensures consistent test data\n- Easy to update test data in one place\n\n---\n\n### 4. Spy and Mock Pattern\n\n**Pattern**: Use spies and mocks to isolate components\n\n```javascript\ndescribe('UserController with mocked service', function() {\n var $scope, controller, mockUserService;\n\n beforeEach(module('myApp'));\n\n // Create a mock service\n beforeEach(module(function($provide) {\n mockUserService = {\n getUsers: jasmine.createSpy('getUsers').and.returnValue([\n { id: 1, name: 'John' }\n ]),\n deleteUser: jasmine.createSpy('deleteUser').and.returnValue(true),\n updateUser: jasmine.createSpy('updateUser').and.callFake(function(user) {\n return Promise.resolve(user);\n })\n };\n\n $provide.value('UserService', mockUserService);\n }));\n\n beforeEach(inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('UserController', {\n $scope: $scope,\n UserService: mockUserService\n });\n }));\n\n it('should call UserService.getUsers on init', function() {\n expect(mockUserService.getUsers).toHaveBeenCalled();\n });\n\n it('should call UserService.deleteUser with correct ID', function() {\n $scope.deleteUser(1);\n expect(mockUserService.deleteUser).toHaveBeenCalledWith(1);\n });\n\n it('should track if user is being updated', function() {\n $scope.updateUser({ id: 1, name: 'Updated' });\n expect(mockUserService.updateUser).toHaveBeenCalled();\n });\n});\n```\n\n**Benefits**:\n- Isolates component from dependencies\n- Tests component logic independently\n- Can verify interactions with dependencies\n\n---\n\n### 5. Helper Function Pattern\n\n**Pattern**: Create helper functions for common test operations\n\n```javascript\ndescribe('UserController with helpers', function() {\n var $scope, controller, $httpBackend;\n\n // Helper functions\n var createController = function(params) {\n return inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('UserController', params || {\n $scope: $scope\n });\n });\n };\n\n var mockHttpUser = function(id, name) {\n $httpBackend.expectGET('/api/users/' + id).respond({\n id: id,\n name: name\n });\n };\n\n var flushAndVerify = function() {\n $httpBackend.flush();\n $httpBackend.verifyNoOutstandingExpectation();\n };\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_$httpBackend_) {\n $httpBackend = _$httpBackend_;\n }));\n\n it('should load user data', function() {\n mockHttpUser(1, 'John');\n createController();\n\n flushAndVerify();\n expect($scope.user.name).toBe('John');\n });\n});\n```\n\n**Benefits**:\n- Reduces code duplication\n- Makes tests more readable\n- Easy to maintain common operations\n\n---\n\n### 6. Error Testing Pattern\n\n**Pattern**: Verify error handling behavior\n\n```javascript\ndescribe('ErrorHandling', function() {\n var service, $httpBackend;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_UserService_, _$httpBackend_) {\n service = _UserService_;\n $httpBackend = _$httpBackend_;\n }));\n\n it('should handle HTTP errors gracefully', function() {\n $httpBackend.expectGET('/api/users').respond(500, 'Server error');\n\n service.getUsers().then(\n function() { /* Should not be called */ },\n function(error) {\n expect(error).toBe('Server error');\n }\n );\n\n $httpBackend.flush();\n });\n\n it('should throw error for invalid input', function() {\n expect(function() {\n service.validateUser(null);\n }).toThrow();\n });\n\n it('should return error object for validation failures', function() {\n var result = service.validate({ email: 'invalid' });\n expect(result.errors).toContain('Invalid email');\n });\n});\n```\n\n**Benefits**:\n- Ensures error handling works correctly\n- Tests error messages are helpful\n- Verifies graceful degradation\n\n---\n\n### 7. Asynchronous Testing Pattern\n\n**Pattern**: Handle promises, timeouts, and async operations\n\n```javascript\ndescribe('Async operations', function() {\n var service, $rootScope, $timeout;\n\n beforeEach(inject(function(_Service_, _$rootScope_, _$timeout_) {\n service = _Service_;\n $rootScope = _$rootScope_;\n $timeout = _$timeout_;\n }));\n\n // Pattern 1: Using $rootScope.$apply()\n it('should resolve promise and update scope', function() {\n var result;\n\n service.asyncOperation().then(function(data) {\n result = data;\n });\n\n $rootScope.$apply();\n expect(result).toBeDefined();\n });\n\n // Pattern 2: Using $timeout.flush()\n it('should handle timeout operations', function() {\n var called = false;\n\n $timeout(function() {\n called = true;\n }, 1000);\n\n expect(called).toBe(false);\n\n $timeout.flush();\n expect(called).toBe(true);\n });\n\n // Pattern 3: Using done() callback (Jasmine)\n it('should handle async with done callback', function(done) {\n service.asyncOperation().then(function(data) {\n expect(data).toBeDefined();\n done();\n });\n\n $rootScope.$apply();\n });\n});\n```\n\n**Benefits**:\n- Properly tests async behavior\n- Prevents test execution before operations complete\n- Verifies timing and order of operations\n\n---\n\n### 8. State Testing Pattern\n\n**Pattern**: Verify component state transitions\n\n```javascript\ndescribe('Component state transitions', function() {\n var $scope, controller;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('MyController', {\n $scope: $scope\n });\n }));\n\n it('should initialize in IDLE state', function() {\n expect($scope.state).toBe('IDLE');\n expect($scope.isLoading).toBe(false);\n });\n\n it('should transition to LOADING when fetching', function() {\n $scope.fetchData();\n\n expect($scope.state).toBe('LOADING');\n expect($scope.isLoading).toBe(true);\n });\n\n it('should transition to LOADED after fetch completes', function() {\n $scope.fetchData();\n $scope.$apply(); // Simulate promise resolution\n\n expect($scope.state).toBe('LOADED');\n expect($scope.isLoading).toBe(false);\n });\n\n it('should transition to ERROR on fetch failure', function() {\n $scope.fetchData();\n $scope.handleError('Connection failed');\n\n expect($scope.state).toBe('ERROR');\n expect($scope.error).toBe('Connection failed');\n });\n});\n```\n\n**Benefits**:\n- Verifies component behaves correctly in different states\n- Tests state transitions\n- Catches state management bugs\n\n---\n\n## Anti-Patterns to Avoid\n\n### ❌ Testing Implementation Details\n\n```javascript\n// BAD: Testing internal state\nit('should set _initialized flag', function() {\n controller.init();\n expect(controller._initialized).toBe(true); // Implementation detail!\n});\n\n// GOOD: Testing behavior\nit('should initialize and be ready to use', function() {\n controller.init();\n expect(controller.getData()).toBeDefined();\n});\n```\n\n### ❌ Over-Mocking\n\n```javascript\n// BAD: Mocking too much\nbeforeEach(module(function($provide) {\n $provide.value('$http', mock$http);\n $provide.value('$timeout', mock$timeout);\n $provide.value('$q', mock$q);\n // ... 10 more mocks\n}));\n\n// GOOD: Mock only external dependencies\nbeforeEach(module(function($provide) {\n $provide.value('UserService', mockUserService);\n}));\n```\n\n### ❌ Test Interdependence\n\n```javascript\n// BAD: Tests depend on execution order\nvar users;\n\nit('should load users', function() {\n users = UserService.getUsers();\n expect(users).toBeDefined();\n});\n\nit('should have users from previous test', function() {\n expect(users.length).toBeGreaterThan(0); // Depends on previous test!\n});\n\n// GOOD: Each test is independent\nit('should load users', function() {\n var users = UserService.getUsers();\n expect(users).toBeDefined();\n});\n\nit('should filter users correctly', function() {\n var users = [\n { active: true },\n { active: false }\n ];\n var active = UserService.filterActive(users);\n expect(active.length).toBe(1);\n});\n```\n\n### ❌ Unclear Test Names\n\n```javascript\n// BAD: Vague test names\nit('should work', function() { ... });\nit('test 1', function() { ... });\nit('error case', function() { ... });\n\n// GOOD: Descriptive test names\nit('should load user data from API when component initializes', function() { ... });\nit('should display error message when API call fails with 500', function() { ... });\nit('should filter active users from list', function() { ... });\n```\n\n---\n\n## Choosing the Right Pattern\n\n| Scenario | Pattern | Why |\n|----------|---------|-----|\n| Simple logic | AAA | Clear structure |\n| User workflows | Given-When-Then | Natural language flow |\n| Complex setup | Test Fixtures | Reduces duplication |\n| Integration testing | Spy and Mock | Isolates components |\n| Repeated operations | Helper Functions | Improves readability |\n| Edge cases | Error Testing | Ensures robustness |\n| Timing issues | Async Testing | Handles timing |\n| Complex logic | State Testing | Tracks state changes |\n\n---\n\n**Last Updated**: January 10, 2026\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":12320,"content_sha256":"0de24f0db075664f532c33d9e7460d7cae0011f06809b43d6e5b7930bae96532"},{"filename":"scripts/run-tests.sh","content":"#!/bin/bash\n\n###############################################################################\n# AngularJS Unit Test Runner\n#\n# Comprehensive bash script for running AngularJS unit tests with various\n# configurations and options.\n#\n# Usage:\n# ./run-tests.sh # Run all tests\n# ./run-tests.sh --watch # Watch mode\n# ./run-tests.sh --coverage # With coverage report\n# ./run-tests.sh --browsers Chrome # Specific browser\n# ./run-tests.sh --single-run # Single test run for CI/CD\n#\n###############################################################################\n\nset -e\n\n# Color output\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nYELLOW='\\033[1;33m'\nNC='\\033[0m' # No Color\n\n# Configuration\nPROJECT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")/../..\" && pwd)\"\nTEST_DIR=\"$PROJECT_DIR/test\"\nCOVERAGE_DIR=\"$PROJECT_DIR/coverage\"\nKARMA_CONFIG=\"$PROJECT_DIR/karma.conf.js\"\n\n# Default options\nWATCH_MODE=false\nCOVERAGE_MODE=false\nSINGLE_RUN=false\nBROWSERS=\"ChromeHeadless\"\nVERBOSE=false\n\n###############################################################################\n# Functions\n###############################################################################\n\n# Print colored output\nprint_status() {\n echo -e \"${GREEN}[TEST]${NC} $1\"\n}\n\nprint_error() {\n echo -e \"${RED}[ERROR]${NC} $1\"\n}\n\nprint_warning() {\n echo -e \"${YELLOW}[WARNING]${NC} $1\"\n}\n\n# Display help\nshow_help() {\n cat \u003c\u003c EOF\nAngularJS Unit Test Runner\n\nUsage: run-tests.sh [options]\n\nOptions:\n --watch Enable watch mode (tests re-run on file changes)\n --coverage Generate code coverage report\n --single-run Run tests once (for CI/CD)\n --browsers BROWSER Specify browser(s) to use (default: ChromeHeadless)\n --verbose Verbose output\n --help Show this help message\n\nExamples:\n ./run-tests.sh\n ./run-tests.sh --watch --coverage\n ./run-tests.sh --single-run --coverage\n ./run-tests.sh --browsers Chrome,Firefox\nEOF\n}\n\n# Parse command line arguments\nparse_arguments() {\n while [[ $# -gt 0 ]]; do\n case $1 in\n --watch)\n WATCH_MODE=true\n shift\n ;;\n --coverage)\n COVERAGE_MODE=true\n shift\n ;;\n --single-run)\n SINGLE_RUN=true\n shift\n ;;\n --browsers)\n BROWSERS=\"$2\"\n shift 2\n ;;\n --verbose)\n VERBOSE=true\n shift\n ;;\n --help)\n show_help\n exit 0\n ;;\n *)\n print_error \"Unknown option: $1\"\n show_help\n exit 1\n ;;\n esac\n done\n}\n\n# Check prerequisites\ncheck_prerequisites() {\n print_status \"Checking prerequisites...\"\n\n if ! command -v npm &> /dev/null; then\n print_error \"npm is not installed\"\n exit 1\n fi\n\n if ! command -v karma &> /dev/null; then\n print_warning \"karma not found globally, will use local installation\"\n KARMA_CMD=\"npx karma\"\n else\n KARMA_CMD=\"karma\"\n fi\n\n if [ ! -f \"$KARMA_CONFIG\" ]; then\n print_error \"karma.conf.js not found at $KARMA_CONFIG\"\n exit 1\n fi\n\n print_status \"Prerequisites check passed ✓\"\n}\n\n# Install dependencies\ninstall_dependencies() {\n print_status \"Installing dependencies...\"\n npm install\n}\n\n# Build test suite\nbuild_tests() {\n print_status \"Building tests...\"\n\n # Check if there's a build step\n if grep -q '\"build:test\"' \"$PROJECT_DIR/package.json\"; then\n npm run build:test\n fi\n}\n\n# Run tests\nrun_tests() {\n print_status \"Running tests...\"\n\n local KARMA_ARGS=\"$KARMA_CONFIG\"\n\n # Add watch mode if enabled\n if [ \"$WATCH_MODE\" = true ]; then\n print_status \"Watch mode enabled - tests will re-run on file changes\"\n else\n KARMA_ARGS=\"$KARMA_ARGS --single-run\"\n fi\n\n # Add coverage if enabled\n if [ \"$COVERAGE_MODE\" = true ]; then\n KARMA_ARGS=\"$KARMA_ARGS --coverage\"\n print_status \"Code coverage enabled\"\n fi\n\n # Add browsers\n KARMA_ARGS=\"$KARMA_ARGS --browsers $BROWSERS\"\n\n # Add verbose if enabled\n if [ \"$VERBOSE\" = true ]; then\n KARMA_ARGS=\"$KARMA_ARGS --log-level DEBUG\"\n fi\n\n # Run karma\n if ! $KARMA_CMD start $KARMA_ARGS; then\n print_error \"Tests failed!\"\n return 1\n fi\n\n return 0\n}\n\n# Generate coverage report\ngenerate_coverage_report() {\n if [ \"$COVERAGE_MODE\" = true ] && [ -d \"$COVERAGE_DIR\" ]; then\n print_status \"Generating coverage report...\"\n\n if [ -f \"$COVERAGE_DIR/index.html\" ]; then\n print_status \"Coverage report generated at: $COVERAGE_DIR/index.html\"\n\n # Try to open coverage report in browser (macOS)\n if command -v open &> /dev/null; then\n open \"$COVERAGE_DIR/index.html\"\n fi\n fi\n fi\n}\n\n# Display test summary\nshow_summary() {\n print_status \"Test run completed\"\n\n if [ \"$COVERAGE_MODE\" = true ]; then\n if [ -f \"$COVERAGE_DIR/index.html\" ]; then\n print_status \"Coverage report available at: $COVERAGE_DIR/index.html\"\n fi\n fi\n\n if [ \"$WATCH_MODE\" = true ]; then\n print_status \"Watch mode active - press Ctrl+C to exit\"\n fi\n}\n\n# Main execution\nmain() {\n parse_arguments \"$@\"\n\n print_status \"AngularJS Unit Test Runner\"\n print_status \"Project directory: $PROJECT_DIR\"\n\n check_prerequisites\n install_dependencies\n build_tests\n\n if run_tests; then\n generate_coverage_report\n show_summary\n exit 0\n else\n print_error \"Test execution failed\"\n exit 1\n fi\n}\n\n# Run main function\nmain \"$@\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5352,"content_sha256":"2c2907362c2a7335ca5db399cdbac764cd7556b5e03e7b3083a8ed924df5c1fd"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"AngularJS Unit Testing Skill","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill specializes in writing, refactoring, and maintaining high-quality unit tests for AngularJS (1.x) applications. It covers controllers, services, filters, directives, HTTP mocking, promises, and dependency injection — everything you need to keep an AngularJS codebase well-tested and reliable.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Note","type":"text","marks":[{"type":"strong"}]},{"text":": AngularJS reached end-of-life in December 2021. It receives only critical security fixes. New projects should use Angular 19+. For teams maintaining AngularJS codebases, this skill provides the latest testing patterns, tooling, and migration guidance. See ","type":"text"},{"text":"Migration Path","type":"text","marks":[{"type":"link","attrs":{"href":"#migration-path-angularjs--angular","title":null}}]},{"text":" at the bottom of this document.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Skill Capabilities","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Core Testing Competencies","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Controllers","type":"text","marks":[{"type":"strong"}]},{"text":": Write tests for controller logic, state management, and event handling","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Services","type":"text","marks":[{"type":"strong"}]},{"text":": Test factory/service dependencies, HTTP calls, and business logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Filters","type":"text","marks":[{"type":"strong"}]},{"text":": Validate filter transformations and edge cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Directives","type":"text","marks":[{"type":"strong"}]},{"text":": Test directive compilation, linking, and DOM manipulation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"HTTP Mocking","type":"text","marks":[{"type":"strong"}]},{"text":": Mock HTTP calls using ","type":"text"},{"text":"$httpBackend","type":"text","marks":[{"type":"code_inline"}]},{"text":" (Jasmine/Karma) or MSW/fetch mocks (Jest)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Promises & Async","type":"text","marks":[{"type":"strong"}]},{"text":": Handle ","type":"text"},{"text":"$q","type":"text","marks":[{"type":"code_inline"}]},{"text":", deferred objects, and ","type":"text"},{"text":"$timeout","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dependency Injection","type":"text","marks":[{"type":"strong"}]},{"text":": Test with mocked and real dependencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Scope Management","type":"text","marks":[{"type":"strong"}]},{"text":": Test scope lifecycle, watchers, and event broadcasting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Coverage Analysis","type":"text","marks":[{"type":"strong"}]},{"text":": Generate and interpret code coverage reports","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test Organization","type":"text","marks":[{"type":"strong"}]},{"text":": Structure tests following best practices and maintainability principles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Framework Choice","type":"text","marks":[{"type":"strong"}]},{"text":": Jest for new work and hybrid repos; Jasmine/Karma for existing suites","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"This skill implements the following testing patterns:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AAA Pattern (Arrange-Act-Assert)","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Organize tests with clear setup, execution, and verification phases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Improves readability and maintainability","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mocking & Spying","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Jasmine spies or Jest mocks to mock functions and verify calls","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mock HTTP responses and service dependencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Simulate user interactions","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test Fixtures","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create reusable test data and helper functions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use beforeEach/afterEach for common setup/teardown","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reduce test duplication","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Edge Case Testing","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test boundary conditions, empty states, and error scenarios","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validate error handling and graceful degradation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test asynchronous operations and race conditions","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Snapshot Testing","type":"text","marks":[{"type":"strong"}]},{"text":" (Jest)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Capture expected component output","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detect unintended changes in UI rendering","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Deterministic Async","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer fake timers, controlled promises, and isolated state over timing-sensitive assertions","type":"text"}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Testing Frameworks & Test Runners","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jasmine (Legacy Default)","type":"text"}]},{"type":"paragraph","content":[{"text":"Jasmine remains the safest choice when you are preserving an existing AngularJS + Karma suite.","type":"text"}]},{"type":"paragraph","content":[{"text":"Key Concepts","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"describe()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Group related tests into a test suite","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"it()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Define individual test cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"expect()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Create assertions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"beforeEach()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"afterEach()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Setup and teardown hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"spyOn()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"jasmine.createSpy()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Mock functions and track calls","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Setup","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npm install --save-dev jasmine karma karma-jasmine karma-chrome-launcher","type":"text"}]},{"type":"paragraph","content":[{"text":"Use when","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"You are maintaining an existing Jasmine/Karma suite","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"You want the smallest possible change set for legacy AngularJS code","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jest (Recommended)","type":"text"}]},{"type":"paragraph","content":[{"text":"Jest is the modern default for AngularJS test maintenance and migration work. It runs tests in parallel, has stronger mocking APIs, supports snapshots, and includes built-in coverage reporting.","type":"text"}]},{"type":"paragraph","content":[{"text":"Key Concepts","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"describe()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Group related tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"test()","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"it()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Define individual test cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"expect()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Create assertions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"beforeEach()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"afterEach()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Setup and teardown hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jest.fn()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Create mock functions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jest.spyOn()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Spy on existing methods","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jest.mock()","type":"text","marks":[{"type":"code_inline"}]},{"text":": Mock modules","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Setup","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npm install --save-dev jest jest-preset-angular angular-mocks\nnpm install @angular/core","type":"text"}]},{"type":"paragraph","content":[{"text":"Jest Configuration","type":"text","marks":[{"type":"strong"}]},{"text":" (","type":"text"},{"text":"jest.config.js","type":"text","marks":[{"type":"code_inline"}]},{"text":"):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"module.exports = {\n preset: 'jest-preset-angular',\n testEnvironment: 'jsdom',\n setupFilesAfterEnv: ['\u003crootDir>/setup-jest.js'],\n transform: {\n '^.+\\.js

AngularJS Unit Testing Skill Overview This skill specializes in writing, refactoring, and maintaining high-quality unit tests for AngularJS (1.x) applications. It covers controllers, services, filters, directives, HTTP mocking, promises, and dependency injection — everything you need to keep an AngularJS codebase well-tested and reliable. Note : AngularJS reached end-of-life in December 2021. It receives only critical security fixes. New projects should use Angular 19+. For teams maintaining AngularJS codebases, this skill provides the latest testing patterns, tooling, and migration guidance.…

: 'babel-jest'\n },\n collectCoverage: true,\n collectCoverageFrom: ['src/**/*.js', '!src/**/*.spec.js']\n};","type":"text"}]},{"type":"paragraph","content":[{"text":"Use when","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"You want faster feedback from parallel execution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"You need better mocking, snapshots, and coverage out of the box","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"You are preparing an AngularJS codebase for an Angular migration","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Karma (Legacy Runner)","type":"text"}]},{"type":"paragraph","content":[{"text":"Karma is the legacy browser test runner traditionally paired with Jasmine. It is still usable for existing suites, but it has seen no major releases since 2021 and should not be the basis for new investment.","type":"text"}]},{"type":"paragraph","content":[{"text":"Configuration","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"karma.conf.js","type":"text","marks":[{"type":"code_inline"}]},{"text":": Main configuration file","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Specifies browser environment, files to load, and plugins","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Supports code coverage reporting and CI integration","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Jest Migration Guide","type":"text"}]},{"type":"paragraph","content":[{"text":"Use these steps when moving an AngularJS test suite from Jasmine/Karma to Jest:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Install the core tooling","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npm install --save-dev jest jest-preset-angular angular-mocks\nnpm install @angular/core","type":"text"}]},{"type":"paragraph","content":[{"text":"Add ","type":"text"},{"text":"@angular/core","type":"text","marks":[{"type":"code_inline"}]},{"text":" when the repo is hybrid or actively migrating toward Angular.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create a Jest setup file","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add ","type":"text"},{"text":"setup-jest.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"setup-jest.ts","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Load ","type":"text"},{"text":"angular","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"angular-mocks","type":"text","marks":[{"type":"code_inline"}]},{"text":", and any shared test polyfills there","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Configure Jest for AngularJS files","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"jest.config.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" with ","type":"text"},{"text":"testEnvironment: 'jsdom'","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Add a transform for legacy JavaScript sources","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep template or DOM-specific setup in the Jest bootstrap file","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Load AngularJS modules in Jest","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"beforeEach(() => {\n require('angular');\n require('angular-mocks');\n angular.mock.module('myApp');\n});","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Migrate spies and stubs","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"spyOn(obj, 'method')","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"jest.spyOn(obj, 'method')","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jasmine.createSpy()","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"jest.fn()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jasmine.createSpyObj()","type":"text","marks":[{"type":"code_inline"}]},{"text":" → ","type":"text"},{"text":"jest.fn()","type":"text","marks":[{"type":"code_inline"}]},{"text":" or explicit mock objects","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Replace ","type":"text","marks":[{"type":"strong"}]},{"text":"$httpBackend","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" where practical","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer ","type":"text"},{"text":"fetch","type":"text","marks":[{"type":"code_inline"}]},{"text":" mocks or MSW for new Jest tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep ","type":"text"},{"text":"$httpBackend","type":"text","marks":[{"type":"code_inline"}]},{"text":" only for legacy tests that are expensive to rewrite immediately","type":"text"}]}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reset state between tests","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"jest.clearAllMocks()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"jest.resetAllMocks()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Recreate AngularJS modules and services in ","type":"text"},{"text":"beforeEach()","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Handling Environmental Flakiness","type":"text"}]},{"type":"paragraph","content":[{"text":"Legacy AngularJS suites often fail because the environment is unstable, not because the code is broken.","type":"text"}]},{"type":"paragraph","content":[{"text":"Common sources of flakiness","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Timing issues and race conditions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shared state between tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Browser environment differences","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Network-dependent tests and real external services","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Time zone, locale, and date-sensitive logic","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Deterministic test patterns","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use fake timers for scheduled work and debounce/throttle logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep async work controlled with explicit promise resolution and digest flushing","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reset shared state, mocks, and module caches in ","type":"text"},{"text":"afterEach()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid real browser/network dependencies in unit tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer fixed test data over generated or time-based values","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"js-env-sanitizer pattern","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Snapshot and restore environment-dependent globals around each test","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Isolate ","type":"text"},{"text":"window","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"document","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"localStorage","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Date","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"Math.random","type":"text","marks":[{"type":"code_inline"}]},{"text":", feature flags, and DOM mutations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"This is especially useful in long-lived AngularJS suites where hidden environment coupling causes intermittent failures","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Test Pyramid Guidance for Legacy Codebases","type":"text"}]},{"type":"paragraph","content":[{"text":"Legacy AngularJS codebases often have an inverted test pyramid: too many end-to-end tests and too few unit tests.","type":"text"}]},{"type":"paragraph","content":[{"text":"Recommended shape","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Base","type":"text","marks":[{"type":"strong"}]},{"text":": many fast unit tests for controllers, services, filters, and directives","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Middle","type":"text","marks":[{"type":"strong"}]},{"text":": fewer integration tests for module wiring, routing, and API boundaries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Top","type":"text","marks":[{"type":"strong"}]},{"text":": a small number of end-to-end tests for critical user journeys only","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Guidance","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Shift coverage toward unit tests first","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep integration tests as the middle layer, not the base","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use e2e tests sparingly because they are slower and more environment-sensitive","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Test Structure","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jasmine Test Structure (Legacy Default)","type":"text"}]},{"type":"paragraph","content":[{"text":"All Jasmine tests follow this standard structure:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('Component Name', function() {\n var componentUnderTest, dependencies;\n\n beforeEach(module('myApp'));\n\n beforeEach(inject(function($injector) {\n componentUnderTest = $injector.get('ComponentName');\n dependencies = $injector.get('DependencyName');\n }));\n\n afterEach(function() {\n // Cleanup code\n });\n\n describe('Functionality Group', function() {\n it('should do something specific', function() {\n // Arrange\n var input = 'test';\n\n // Act\n var result = componentUnderTest.method(input);\n\n // Assert\n expect(result).toBe('expected');\n });\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jest Test Structure (Recommended)","type":"text"}]},{"type":"paragraph","content":[{"text":"Jest tests follow a similar structure with modern mocking and cleaner teardown:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('Component Name', () => {\n let componentUnderTest;\n let dependency;\n\n beforeEach(() => {\n jest.clearAllMocks();\n // Setup code or mock initialization\n dependency = { method: jest.fn() };\n componentUnderTest = require('./component');\n });\n\n afterEach(() => {\n // Cleanup code\n });\n\n describe('Functionality Group', () => {\n test('should do something specific', () => {\n // Arrange\n const input = 'test';\n\n // Act\n const result = componentUnderTest.method(input, dependency);\n\n // Assert\n expect(result).toBe('expected');\n });\n });\n});","type":"text"}]},{"type":"paragraph","content":[{"text":"Key Differences","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest uses ","type":"text"},{"text":"jest.fn()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"jest.spyOn()","type":"text","marks":[{"type":"code_inline"}]},{"text":" instead of Jasmine spies for modern test code","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest uses ","type":"text"},{"text":"test()","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"it()","type":"text","marks":[{"type":"code_inline"}]},{"text":" (both work)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest auto-discovers ","type":"text"},{"text":".spec.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":".test.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" files","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest provides built-in snapshot testing and code coverage","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"1. Test Organization","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"One test file per component (e.g., ","type":"text"},{"text":"controller.spec.js","type":"text","marks":[{"type":"code_inline"}]},{"text":" for ","type":"text"},{"text":"controller.js","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Organize tests into logical groups using ","type":"text"},{"text":"describe()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use meaningful test names that describe expected behavior","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"2. DRY Principle (Don't Repeat Yourself)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extract common setup into ","type":"text"},{"text":"beforeEach()","type":"text","marks":[{"type":"code_inline"}]},{"text":" blocks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Create reusable test data fixtures","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use helper functions to reduce duplication","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"3. Test Independence","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Each test should be independent and runnable in any order","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clean up resources in ","type":"text"},{"text":"afterEach()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid shared state between tests","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"4. Realistic Mocks","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mock external dependencies realistically","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use actual data structures when possible","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid overly simplified or unrealistic mocks","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"5. Comprehensive Coverage","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Aim for 80%+ code coverage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test happy paths, edge cases, and error scenarios","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test async operations and race conditions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"6. Performance","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keep tests fast","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use in-memory mocks instead of real HTTP requests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid unnecessary database operations","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"7. Maintainability","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Write tests that are easy to understand","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use the AAA pattern for clarity","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Document complex test logic with comments","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refactor tests when the component changes","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Testing Scenarios","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Controllers","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('UserController', function() {\n var $scope, controller;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function($controller, $rootScope) {\n $scope = $rootScope.$new();\n controller = $controller('UserController', {\n $scope: $scope\n });\n }));\n\n it('should initialize with default values', function() {\n expect($scope.users).toBeDefined();\n });\n\n it('should load users on init', function() {\n expect($scope.users.length).toBeGreaterThan(0);\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Services","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('UserService', function() {\n var userService, $httpBackend;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_UserService_, _$httpBackend_) {\n userService = _UserService_;\n $httpBackend = _$httpBackend_;\n }));\n\n afterEach(function() {\n $httpBackend.verifyNoOutstandingExpectation();\n });\n\n it('should fetch users from API', function() {\n var expectedUsers = [{ id: 1, name: 'John' }];\n $httpBackend.expectGET('/api/users').respond(expectedUsers);\n\n userService.getUsers().then(function(users) {\n expect(users).toEqual(expectedUsers);\n });\n\n $httpBackend.flush();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing with Promises","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('PromiseService', function() {\n var service, $q, $rootScope;\n\n beforeEach(inject(function(_Service_, _$q_, _$rootScope_) {\n service = _Service_;\n $q = _$q_;\n $rootScope = _$rootScope_;\n }));\n\n it('should handle promise resolution', function() {\n var deferred = $q.defer();\n var result;\n\n service.asyncOperation().then(function(data) {\n result = data;\n });\n\n deferred.resolve('success');\n $rootScope.$apply();\n\n expect(result).toBe('success');\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Component Directives","type":"text"}]},{"type":"paragraph","content":[{"text":"AngularJS 1.5+ component directives (","type":"text"},{"text":"bindings","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"controllerAs","type":"text","marks":[{"type":"code_inline"}]},{"text":") are the recommended pattern for new code and the easiest to migrate to Angular later.","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('userCard component', function() {\n var $compile, $rootScope, element, scope;\n\n beforeEach(module('myApp'));\n beforeEach(inject(function(_$compile_, _$rootScope_) {\n $compile = _$compile_;\n $rootScope = _$rootScope_;\n scope = $rootScope.$new();\n scope.user = { name: 'Alice', role: 'admin' };\n }));\n\n it('should render user name and role', function() {\n element = $compile('\u003cuser-card user=\"user\">\u003c/user-card>')(scope);\n scope.$digest();\n\n var isolated = element.isolateScope().$ctrl;\n expect(isolated.user.name).toBe('Alice');\n expect(element.text()).toContain('admin');\n });\n\n it('should call onSelect when clicked', function() {\n scope.onSelect = jasmine.createSpy('onSelect');\n element = $compile('\u003cuser-card user=\"user\" on-select=\"onSelect(user)\">\u003c/user-card>')(scope);\n scope.$digest();\n\n element.isolateScope().$ctrl.onSelect({ user: scope.user });\n expect(scope.onSelect).toHaveBeenCalledWith(scope.user);\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing $httpBackend with Request Matchers","type":"text"}]},{"type":"paragraph","content":[{"text":"For APIs with dynamic segments or query parameters, use regex or function matchers instead of exact URL strings:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Match any GET to /api/users with query params\n$httpBackend.expectGET(/\\/api\\/users\\?.*page=/).respond(200, mockResponse);\n\n// Match by function\n$httpBackend.expectGET(function(url) {\n return url.indexOf('/api/users') === 0 && url.indexOf('page=') > -1;\n}).respond(200, mockResponse);","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing $on / $broadcast Events","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('event-driven service', function() {\n var $rootScope, service;\n\n beforeEach(inject(function(_$rootScope_, _EventService_) {\n $rootScope = _$rootScope_;\n service = _EventService_;\n }));\n\n it('should react to user:updated event', function() {\n var handler = jasmine.createSpy('handler');\n $rootScope.$on('user:updated', handler);\n\n $rootScope.$broadcast('user:updated', { id: 42 });\n expect(handler).toHaveBeenCalled();\n expect(handler.calls.argsFor(0)[1]).toEqual({ id: 42 });\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Debugging Tests","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jasmine Debugging","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Skip a test\nxit('should do something', function() { ... });\n\n// Run only this test\nfit('should do something', function() { ... });\n\n// Console logging in tests\nit('should debug', function() {\n console.log('Current state:', $scope);\n expect(true).toBe(true);\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jest Debugging","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Skip a test\ntest.skip('should do something', () => { ... });\n\n// Run only this test\ntest.only('should do something', () => { ... });\n\n// Debug with Node inspector\n// Run: node --inspect-brk node_modules/.bin/jest --runInBand","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Integration with CI/CD","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"GitHub Actions Example","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"name: Test\non: [push, pull_request]\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: '22'\n cache: npm\n - run: npm ci\n - run: npm test -- --coverage\n - uses: codecov/codecov-action@v4\n with:\n files: ./coverage/lcov.info","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Jenkins Example","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"groovy"},"content":[{"text":"pipeline {\n stages {\n stage('Test') {\n steps {\n sh 'npm ci'\n sh 'npm test'\n publishHTML([\n reportDir: 'coverage',\n reportFiles: 'index.html',\n reportName: 'Coverage Report'\n ])\n }\n }\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jasmine Documentation","type":"text","marks":[{"type":"link","attrs":{"href":"https://jasmine.github.io/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Karma Test Runner","type":"text","marks":[{"type":"link","attrs":{"href":"https://karma-runner.github.io/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest Documentation","type":"text","marks":[{"type":"link","attrs":{"href":"https://jestjs.io/","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jest-preset-angular","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/thymikee/jest-preset-angular","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AngularJS Testing Guide","type":"text","marks":[{"type":"link","attrs":{"href":"https://docs.angularjs.org/guide/unit-testing","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Angular Testing Guide","type":"text","marks":[{"type":"link","attrs":{"href":"https://angular.dev/guide/testing","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Angular Update Guide","type":"text","marks":[{"type":"link","attrs":{"href":"https://angular.dev/update-guide","title":null}}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Tests not running","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check that module is loaded: ","type":"text"},{"text":"beforeEach(module('myApp'))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify dependencies are injected: ","type":"text"},{"text":"beforeEach(inject(...))","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check for syntax errors","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Async tests timing out","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Ensure promises are resolved: ","type":"text"},{"text":"$rootScope.$apply()","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"$httpBackend.flush()","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"done()","type":"text","marks":[{"type":"code_inline"}]},{"text":" callback: ","type":"text"},{"text":"it('...', function(done) { ... done(); })","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For Jest: use ","type":"text"},{"text":"async/await","type":"text","marks":[{"type":"code_inline"}]},{"text":" or return a promise","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"HTTP mocks not working","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify mock is set up before service call","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Check exact URL match in expectations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"passThrough()","type":"text","marks":[{"type":"code_inline"}]},{"text":" for unmocked requests","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Coverage gaps","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Review untested branches in coverage reports","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test error conditions and edge cases","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mock external dependencies properly","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Modern Runtime Compatibility","type":"text"}]},{"type":"paragraph","content":[{"text":"AngularJS was built for an older Node.js and browser ecosystem, but it still runs on modern runtimes with the right configuration.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Node.js 22 LTS","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"AngularJS 1.8.x runs on Node 22 LTS without modification","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If your tests use Karma with a real browser, use ","type":"text"},{"text":"karma-chrome-launcher","type":"text","marks":[{"type":"code_inline"}]},{"text":" with headless Chrome","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For Jest: ","type":"text"},{"text":"testEnvironment: 'jsdom'","type":"text","marks":[{"type":"code_inline"}]},{"text":" handles the browser globals — no real browser needed","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Headless Chrome in CI","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"yaml"},"content":[{"text":"# GitHub Actions — headless Chrome with Karma\n- uses: actions/setup-chrome@v1\n with:\n chrome-version: stable\n- run: npm test","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// karma.conf.js — headless Chrome for CI\nbrowsers: ['ChromeHeadlessNoSandbox'],\ncustomLaunchers: {\n ChromeHeadlessNoSandbox: {\n base: 'ChromeHeadless',\n flags: ['--no-sandbox']\n }\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common Pitfalls on Modern Runtimes","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"V8 strict mode","type":"text","marks":[{"type":"strong"}]},{"text":": AngularJS code relying on implicit globals or ","type":"text"},{"text":"arguments.callee","type":"text","marks":[{"type":"code_inline"}]},{"text":" will fail in strict mode. Use ","type":"text"},{"text":"'use strict'","type":"text","marks":[{"type":"code_inline"}]},{"text":" in test files to catch these early.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jsdom vs. real browser","type":"text","marks":[{"type":"strong"}]},{"text":": Some directive tests that depend on real layout or CSS computation may not work in jsdom. Run those with Karma + headless Chrome instead.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"npm audit failures","type":"text","marks":[{"type":"strong"}]},{"text":": AngularJS dependencies may have known vulnerabilities. Use ","type":"text"},{"text":"npm audit --production","type":"text","marks":[{"type":"code_inline"}]},{"text":" to separate real risks from dev-only warnings, and consider ","type":"text"},{"text":"overrides","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"package.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" to pin patched transitive dependencies.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Migration Path: AngularJS → Angular","type":"text"}]},{"type":"paragraph","content":[{"text":"When the time comes to move off AngularJS, here is the conceptual mapping:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"AngularJS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Angular 19+","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Modules / controllers","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Standalone components or NgModules, injectable services","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"$scope","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"$rootScope","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Component state, ","type":"text"},{"text":"@Input()","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"@Output()","type":"text","marks":[{"type":"code_inline"}]},{"text":", signals","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"$http","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"$resource","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"HttpClient","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Directives","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Components and directives with modern APIs","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"$q","type":"text","marks":[{"type":"code_inline"}]},{"text":" / digest cycle","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"RxJS, promises/async-await, signals","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"$routeProvider","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Angular Router","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"angular.module()","type":"text","marks":[{"type":"code_inline"}]},{"text":" DI","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tree-shakable providers, ","type":"text"},{"text":"inject()","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Globals and ad-hoc DOM","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dependency injection and testable abstractions","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Hybrid migration","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"@angular/upgrade","type":"text","marks":[{"type":"code_inline"}]},{"text":" to run AngularJS and Angular side by side. Migrate component-by-component rather than rewriting the whole app at once. The AngularJS test suite stays active throughout — Jest is the best runner for hybrid repos because it handles both AngularJS and Angular test files.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Next Steps","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Start Small","type":"text","marks":[{"type":"strong"}]},{"text":": Write tests for a single component","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Choose the Right Runner","type":"text","marks":[{"type":"strong"}]},{"text":": Keep Jasmine/Karma only for existing suites; prefer Jest for new work and hybrid repos","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Understand Patterns","type":"text","marks":[{"type":"strong"}]},{"text":": Study the testing patterns guide","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Templates","type":"text","marks":[{"type":"strong"}]},{"text":": Reference template files for your component type","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refine","type":"text","marks":[{"type":"strong"}]},{"text":": Improve tests based on coverage and feedback","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Automate","type":"text","marks":[{"type":"strong"}]},{"text":": Integrate tests into your CI/CD pipeline","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Plan the Migration","type":"text","marks":[{"type":"strong"}]},{"text":": When ready, use ","type":"text"},{"text":"@angular/upgrade","type":"text","marks":[{"type":"code_inline"}]},{"text":" for incremental migration — your test suite migrates with you","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Specialization","type":"text","marks":[{"type":"strong"}]},{"text":": AngularJS Unit Testing with Jasmine and Jest","type":"text"},{"type":"br"},{"text":"Version","type":"text","marks":[{"type":"strong"}]},{"text":": 2.0","type":"text"},{"type":"br"},{"text":"Last Updated","type":"text","marks":[{"type":"strong"}]},{"text":": May 2026","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"angularjs-unit-testing","author":"@skillopedia","source":{"stars":8,"repo_name":"agent-skills","origin_url":"https://github.com/greedychipmunk/agent-skills/blob/HEAD/angularjs-unit-test/skill.md","repo_owner":"greedychipmunk","body_sha256":"07c53d653d09da6ba36d2750a4f8463cf8a77357454cc55074fe20364d8c2e74","cluster_key":"d54951627b16cddd4d147a6469b3c7f9fbe3db47354f1ca63c7a9e769b3ae47b","clean_bundle":{"format":"clean-skill-bundle-v1","source":"greedychipmunk/agent-skills/angularjs-unit-test/skill.md","attachments":[{"id":"40f66b6a-e477-5757-b40f-5bdb00e4912e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/40f66b6a-e477-5757-b40f-5bdb00e4912e/attachment.md","path":"INDEX.md","size":6785,"sha256":"fc8284ae7b88d491cf407ba4c290e6e77d353c04182b303f0a3d2b8c04a0bc7f","contentType":"text/markdown; charset=utf-8"},{"id":"796d86a9-0c1e-52c3-8518-f42f9ff5c830","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/796d86a9-0c1e-52c3-8518-f42f9ff5c830/attachment.md","path":"README.md","size":10682,"sha256":"e9f6ced4943f3a2cdf81fb38fa5ec2bc20daafca962689080b418fe125f89e99","contentType":"text/markdown; charset=utf-8"},{"id":"1304167e-2877-5e47-9177-698dbcaef047","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1304167e-2877-5e47-9177-698dbcaef047/attachment.md","path":"resources/angularjs-testing-reference.md","size":9463,"sha256":"d61c6bbbb2f3c732a101db209366c3c12d596e048721240614fe04c482d04a6a","contentType":"text/markdown; charset=utf-8"},{"id":"41bd6539-1cbc-5fcf-af87-f894efc5f815","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/41bd6539-1cbc-5fcf-af87-f894efc5f815/attachment.md","path":"resources/best-practices-checklist.md","size":7641,"sha256":"6b3ac7ff64a08db6e98eb9776858b129b8127efc3ae051d19fc7d7c4eedb5150","contentType":"text/markdown; charset=utf-8"},{"id":"427ca093-98d1-5742-a130-979b264ccae7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/427ca093-98d1-5742-a130-979b264ccae7/attachment.md","path":"resources/coverage-analysis-guide.md","size":7624,"sha256":"b1db1a5178bebb95af2ef527502a17de41cee634b077e9af456a9741a86eb96f","contentType":"text/markdown; charset=utf-8"},{"id":"efba3f96-e1ff-5087-8bba-09f16a0cdc7c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/efba3f96-e1ff-5087-8bba-09f16a0cdc7c/attachment.md","path":"resources/http-mocking-guide.md","size":11105,"sha256":"d1a7e204db7b1587fbf5e055d8725350019bde44977f5d14de06e49b8dd6b8e5","contentType":"text/markdown; charset=utf-8"},{"id":"4026f105-5241-52d4-bc25-f7304ff94d34","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4026f105-5241-52d4-bc25-f7304ff94d34/attachment.md","path":"resources/jest-migration-guide.md","size":4453,"sha256":"496053067d906f630d1f5e9693409c26603f90c71c1937baad062360391137df","contentType":"text/markdown; charset=utf-8"},{"id":"f9eea185-7bad-5297-9929-0c673807db5e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f9eea185-7bad-5297-9929-0c673807db5e/attachment.md","path":"resources/mock-helpers-guide.md","size":10089,"sha256":"1823eab7dff64183e0abce4481b2c634807ba1fe00e5e5584187ab5cb43b0495","contentType":"text/markdown; charset=utf-8"},{"id":"8f73798e-f33f-5c57-bde1-e7d86e7e9def","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/8f73798e-f33f-5c57-bde1-e7d86e7e9def/attachment.md","path":"resources/testing-patterns-guide.md","size":12320,"sha256":"0de24f0db075664f532c33d9e7460d7cae0011f06809b43d6e5b7930bae96532","contentType":"text/markdown; charset=utf-8"},{"id":"9d1f3be4-3b59-5a1b-9645-d7ab620fa2ef","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9d1f3be4-3b59-5a1b-9645-d7ab620fa2ef/attachment.sh","path":"scripts/run-tests.sh","size":5352,"sha256":"2c2907362c2a7335ca5db399cdbac764cd7556b5e03e7b3083a8ed924df5c1fd","contentType":"application/x-sh; charset=utf-8"},{"id":"1c57ab14-b6cc-598e-a491-457f9af3667b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1c57ab14-b6cc-598e-a491-457f9af3667b/attachment.js","path":"templates/controller.spec.js","size":14327,"sha256":"92614a529929d80e6c08b98a5ebb3d407818d42ddbd1e8543fc7ee432c9b6a7a","contentType":"application/javascript; charset=utf-8"},{"id":"c60ce3e8-1d7d-55e6-91f2-dffd0af1b953","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c60ce3e8-1d7d-55e6-91f2-dffd0af1b953/attachment.js","path":"templates/directive.spec.js","size":1913,"sha256":"17892132d5719dd6500a732d275da8e866ce194d703386c0b7749c7878702ff3","contentType":"application/javascript; charset=utf-8"},{"id":"c17b6585-db7f-5ede-a908-1aeb44da8a56","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c17b6585-db7f-5ede-a908-1aeb44da8a56/attachment.js","path":"templates/filter.spec.js","size":1553,"sha256":"0e06857cda7378de5ed8df73d407236801cfda0fe60aeee854e2fe63048fe6b2","contentType":"application/javascript; charset=utf-8"},{"id":"cb1dea1c-6c73-52cf-8ce7-3300a3e3a909","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cb1dea1c-6c73-52cf-8ce7-3300a3e3a909/attachment.js","path":"templates/fixtures.js","size":10516,"sha256":"34cb768dcf108d5c01900cdd64d9b069130684c570758598adbeb792a108bf34","contentType":"application/javascript; charset=utf-8"},{"id":"d7d528e9-ec30-5a13-ad6c-bd5e3d541447","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d7d528e9-ec30-5a13-ad6c-bd5e3d541447/attachment.js","path":"templates/service.spec.js","size":17267,"sha256":"5090813c58d2a3b7199dd6ee9e4015a36d2beab13862e2033bc23907e19bb9d3","contentType":"application/javascript; charset=utf-8"}],"bundle_sha256":"99dacb0db73029187e167605276a2d1e71558699c35705eb8095a4d3380bb208","attachment_count":15,"text_attachments":15,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"angularjs-unit-test/skill.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"testing-qa","import_tag":"clean-skills-v1","description":"Use this skill for AngularJS unit testing, maintenance, and migration tasks"}},"renderedAt":1782979287556}

AngularJS Unit Testing Skill Overview This skill specializes in writing, refactoring, and maintaining high-quality unit tests for AngularJS (1.x) applications. It covers controllers, services, filters, directives, HTTP mocking, promises, and dependency injection — everything you need to keep an AngularJS codebase well-tested and reliable. Note : AngularJS reached end-of-life in December 2021. It receives only critical security fixes. New projects should use Angular 19+. For teams maintaining AngularJS codebases, this skill provides the latest testing patterns, tooling, and migration guidance.…