J'ai essayé de rendre certains modèles liés à un état et à un composant conformément à cet article article
Dans mon projet fonctionnant sous dev-server, tout fonctionne correctement et lorsque j'exécute $state.go("home")
, le modèle de composant est chargé comme prévu, mais lorsque je le fais dans un environnement de test, cela ne fonctionne pas.
Auparavant, lors des tests, lorsque j'utilisais "l'ancienne méthode" en utilisant "modèle" à la place "composant" avec ui-router, exécuter $rootScope.$digest()
suffisait pour ajouter le modèle dans le <div ui-view></div>
, mais en utilisant cette nouvelle méthode, cela ne fonctionnait plus.
Qu'est-ce que je fais mal?
Edit: J'ai essayé de comprendre le problème en profondeur et je constate que le problème est lié à la requête HTTP effectuée. Peut-être est-ce lié à la façon dont ma promesse résout le rappel de résolution en utilisant async/wait. S'il vous plaît vérifier le service:
Un service
export class TodoService {
constructor($http, BASE_URL) {
this.http = $http;
this.url = `${BASE_URL}/todos`
}
async getTodos() {
const apiResponse = await this.http.get(this.url)
return apiResponse.data.todos
}
}
Routeur
import '@uirouter/angularjs'
export function routes($stateProvider, $locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false,
rewriteLinks: true,
})
$stateProvider
.state("home", {
url: "/",
component: "todoList",
resolve: {
todosList: TodoService => TodoService.getTodos()
}
})
}
Test
import { routes } from "routes"
import { TodoListComponent } from "components/todoList.component"
import { TodoService } from "services/todo.service"
describe("TodoListComponent rendering and interaction on '/' base path", () => {
let componentDOMelement
let stateService
beforeAll(() => {
angular
.module("Test", [
"ui.router"
])
.config(routes)
.constant("BASE_URL", "http://localhost:5000/api")
.component("todoList", TodoListComponent)
.service("TodoService", TodoService)
//I enable this for better logs about the problem
.run(['$rootScope','$trace', function($rootScope, $trace) {
$trace.enable("TRANSITION")
}])
})
beforeEach(angular.mock.module("Test"))
beforeEach(inject(($rootScope, $compile, $state, $httpBackend) => {
//build the scene
//1st render the root element of scene: We needs a router view for load the base path
let scope = $rootScope.$new()
componentDOMelement = angular.element("<div ui-view></div>")
$compile(componentDOMelement)(scope)
scope.$digest()
document.body.appendChild(componentDOMelement[0]) //This is a hack for jsdom before the $rootScope.$digest() call
//2nd let's create a fake server for intercept the http requests and fake the responses
const todosResponse = require(`${__dirname}/../../stubs/todos_get.json`)
$httpBackend
.whenGET(/.+\/todos/)
.respond((method, url, data, headers, params) => {
return [200, todosResponse]
})
//3rd Let's generate the basic scenario: Go at home state ("/" path)
$state.go("home")
$rootScope.$digest()
$httpBackend.flush()
}))
it("Should be render a list", () => {
console.log("HTML rendered")
console.log(document.querySelectorAll("html")[0].outerHTML)
})
})
Le résultat HTML qui ne rend pas
<html>
<head>
<style type="text/css">
@charset "UTF-8";[ng\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate) {
display:none !important;
}
ng\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{
position:absolute;
}
</style>
</head>
<body><!-- uiView: -->
</body>
</html>
En outre, j'ai tracé l'étatChange avant le HTML:
console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1276
Transition #0-0: Started -> "Transition#0( ''{} -> 'home'{} )"
console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1282
Transition #1-0: Ignored <> "Transition#1( ''{} -> 'home'{} )"
console.log node_modules/@uirouter/core/_bundles/ui-router-core.js:1313
Transition #1-0: <- Rejected "Transition#1( ''{} -> 'home'{} )", reason: Transition Rejection($id: 0 type: 5, message: The transition was ignored, detail: "undefined")
Je vois un problème dans une transition mais aucune raison n'a été donnée.
=============================================== =======================
Edit 2Enfin, nous avons trouvé le problème mais je n'arrive pas à comprendre le vrai problème. J'ai créé une branche dans mon projet pour montrer le problème. Ceci est lié à la fonctionnalité javascript async/await
:
export class TodoService {
constructor($http, BASE_URL) {
this.http = $http;
this.url = `${BASE_URL}/todos`
}
//Interchange the comment on the getTodos method and run `npm run tdd` for see the problem:
//When async/await doesn't used, the html associated to the resolve in the
// "/" route that used this service, the promise was resolved that expected.
//The idea for this branch it's research about the problem and propose a way
//for we can use async/await on the production code and on the testing environment
async getTodos() {
const apiResponse = await this.http.get(this.url)
return apiResponse.data.todos
}
// getTodos() {
// return this.http.get(this.url).then(res => res.data.todos)
// }
}
Donc mes nouvelles questions sont:
Edit 3 Le problème 3522 signalé dans le référentiel de routeurs d'interface utilisateur angulaire
Le problème, c’est que angular s’attend à une promesse angulaire, c’est la raison pour laquelle votre travail fonctionnera mais que votre attente ne fonctionnera pas. Vous pouvez résoudre ce problème en utilisant une bibliothèque du type: https://www.npmjs.com/package/angular-async- attendre ou faire une construction comme ils le démontrent ici https://medium.com/@alSkachkov/using-async-await-function-in-angular-1-5-babel-6-387f7c43948c
Bonne chance avec votre problème!
Ceci est juste une supposition éclairée basée sur ma compréhension de la façon dont resolve
ers fonctionnent et ce que ngMock
fait. Dans votre premier exemple, votre resolve
pour getTodos
ne sera renvoyée que lorsque la promesse $http
aura été résolue. À ce stade, vous extrayerez la valeur de la réponse et vous la retournerez. Cependant, resolve
ers attend une valeur $q.Promise
en tant que sentinelle pour conserver le rendu du routeur jusqu'à sa résolution. Dans votre code, en fonction de la manière dont il est transpilé, l'appel await
et return
ne produit probablement pas la valeur sentinelle correcte; il est donc traité comme une réponse synchrone.
Une méthode de test consiste à demander la variable resolve
- dans le contrôleur pour votre composant todolist
et à inspecter la valeur. Je parie que c'estPASun $q.Promise
, bien que cela puisse être une promesse native.
Cependant, lorsque vous utilisez resolve
, enchaînez simplement la promesse en ajoutant un then
et renvoyez-la. Le routeur se chargera du reste.
Ou mieux, passez à Observables! (/ moi les canards arrivent des tomates)