From 33bdff92346ce165b1e72f36013be892b93f1de0 Mon Sep 17 00:00:00 2001 From: Crismar Mejia Date: Thu, 7 Sep 2023 14:25:22 -0400 Subject: [PATCH] implemented poi create/edit with htmx --- guide.go | 1 + server.go | 195 +++++++++++++++++-- server_test.go | 330 ++++++++++++++++++--------------- store_test.go | 2 +- templates/createGuideForm.html | 8 +- templates/createPoiForm.html | 12 +- templates/editPoiForm.html | 50 +++++ templates/guide.html | 38 ++-- templates/poiRows.html | 38 ++-- 9 files changed, 462 insertions(+), 212 deletions(-) create mode 100644 templates/editPoiForm.html diff --git a/guide.go b/guide.go index fd368d6..0c4523a 100644 --- a/guide.go +++ b/guide.go @@ -158,6 +158,7 @@ type guideForm struct { } type poiForm struct { + PoiID int64 GuideID int64 GuideName string Name, Description, Latitude, Longitude string diff --git a/server.go b/server.go index ce0d365..7f52cad 100644 --- a/server.go +++ b/server.go @@ -51,7 +51,7 @@ func (s *Server) HandleGuides() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { terms := r.URL.Query().Get("q") guides, err := s.store.Search(terms) - if err != nil || len(guides) == 0 { + if err != nil || (len(guides) == 0 && terms != "") { http.Error(w, "no guide found", http.StatusNotFound) return } @@ -170,7 +170,7 @@ func (s *Server) HandleEditGuideGet() http.HandlerFunc { } g, err := s.store.GetGuidebyID(id) if err != nil { - http.Error(w, "db error", http.StatusNotFound) + http.Error(w, "internal server error", http.StatusNotFound) return } if g == nil { @@ -208,11 +208,11 @@ func (s *Server) HandleEditGuidePost() http.HandlerFunc { } g, err := s.store.GetGuidebyID(id) if err != nil { - http.Error(w, "guide Not Found", http.StatusNotFound) + http.Error(w, "internal server error", http.StatusInternalServerError) return } - if g.Id == 0 { - http.Error(w, "guide not found", http.StatusNotFound) + if g == nil { + http.Error(w, "guide Not Found", http.StatusNotFound) return } @@ -319,6 +319,8 @@ func (s *Server) HandlePoi() http.HandlerFunc { } } } + +// todo implement HandlePois() to map poiRows(clean #table-and-form): cancel button, and search func (s *Server) HandleCreatePoiGet() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { guideID := mux.Vars(r)["id"] @@ -329,12 +331,16 @@ func (s *Server) HandleCreatePoiGet() http.HandlerFunc { gid, err := strconv.ParseInt(guideID, 10, 64) if err != nil { - http.Error(w, "please provide valid guide Id", http.StatusBadRequest) + http.Error(w, "please provide valid guide PoiID", http.StatusBadRequest) } g, err := s.store.GetGuidebyID(gid) if err != nil { - http.Error(w, "guide not found", http.StatusNotFound) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if g == nil { + http.Error(w, "guide Not Found", http.StatusNotFound) return } poiForm := poiForm{ @@ -346,7 +352,7 @@ func (s *Server) HandleCreatePoiGet() http.HandlerFunc { Longitude: r.PostFormValue("longitude"), } - err = s.templateRegistry.renderPage(w, createPoiFormTemplate, poiForm) + err = s.templateRegistry.renderPartial(w, createPoiFormTemplate, poiForm) if err != nil { http.Error(w, "internal server error", http.StatusInternalServerError) } @@ -356,31 +362,35 @@ func (s *Server) HandleCreatePoiGet() http.HandlerFunc { func (s *Server) HandleCreatePoiPost() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - guideID := mux.Vars(r)["id"] - if guideID == "" { + guideIDString := mux.Vars(r)["id"] + if guideIDString == "" { http.Error(w, "no guideid provided", http.StatusBadRequest) return } - gid, err := strconv.ParseInt(guideID, 10, 64) + guideID, err := strconv.ParseInt(guideIDString, 10, 64) if err != nil { - http.Error(w, "please provide valid guide Id", http.StatusBadRequest) + http.Error(w, "please provide valid guide id", http.StatusBadRequest) } - g, err := s.store.GetGuidebyID(gid) + g, err := s.store.GetGuidebyID(guideID) if err != nil { - http.Error(w, "guide not found", http.StatusNotFound) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if g == nil { + http.Error(w, "guide Not Found", http.StatusNotFound) return } poiForm := poiForm{ - GuideID: gid, + GuideID: guideID, GuideName: g.Name, Name: r.PostFormValue("name"), Description: r.PostFormValue("description"), Latitude: r.PostFormValue("latitude"), Longitude: r.PostFormValue("longitude"), } - poi, err := NewPointOfInterest(poiForm.Name, gid, PoiWithValidStringCoordinates(poiForm.Latitude, poiForm.Longitude), PoiWithDescription(poiForm.Description)) + poi, err := NewPointOfInterest(poiForm.Name, guideID, PoiWithValidStringCoordinates(poiForm.Latitude, poiForm.Longitude), PoiWithDescription(poiForm.Description)) if err != nil { poiForm.Errors = append(poiForm.Errors, err.Error()) w.WriteHeader(http.StatusBadRequest) @@ -401,8 +411,150 @@ func (s *Server) HandleCreatePoiPost() http.HandlerFunc { return } - gURL := fmt.Sprintf("/guide/%d", gid) - http.Redirect(w, r, gURL, http.StatusSeeOther) + pois := s.store.GetAllPois(guideID) + err = s.templateRegistry.renderPartial(w, poiRowsTemplate, pois) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + } + } +} +func (s *Server) HandleEditPoiGet() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + guideIDString := mux.Vars(r)["guideID"] + if guideIDString == "" { + http.Error(w, "no guide ID provided", http.StatusBadRequest) + return + } + poiIDString := mux.Vars(r)["poiID"] + if poiIDString == "" { + http.Error(w, "no poi ID provided", http.StatusBadRequest) + return + } + + guideID, err := strconv.ParseInt(guideIDString, 10, 64) + if err != nil { + http.Error(w, "not able to parse guide ID", http.StatusBadRequest) + return + } + poiID, err := strconv.ParseInt(poiIDString, 10, 64) + if err != nil { + http.Error(w, "not able to parse poi ID", http.StatusBadRequest) + return + } + + g, err := s.store.GetGuidebyID(guideID) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if g == nil { + http.Error(w, "guide Not Found", http.StatusNotFound) + return + } + + poi, err := s.store.GetPoi(guideID, poiID) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if poi == nil { + http.Error(w, "guide Not Found", http.StatusNotFound) + return + } + + poiForm := poiForm{ + PoiID: poiID, + GuideID: g.Id, + GuideName: g.Name, + Name: poi.Name, + Description: poi.Description, + Latitude: fmt.Sprintf("%f", poi.Coordinate.Latitude), + Longitude: fmt.Sprintf("%f", poi.Coordinate.Longitude), + Errors: []string{}, + } + + err = s.templateRegistry.renderPartial(w, editPoiFormTemplate, poiForm) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + } + return + } +} +func (s *Server) HandleEditPoiPatch() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + guideIDString := mux.Vars(r)["guideID"] + if guideIDString == "" { + http.Error(w, "no guide ID provided", http.StatusBadRequest) + return + } + poiIDString := mux.Vars(r)["poiID"] + if poiIDString == "" { + http.Error(w, "no poi ID provided", http.StatusBadRequest) + return + } + + guideID, err := strconv.ParseInt(guideIDString, 10, 64) + if err != nil { + http.Error(w, "not able to parse guide ID", http.StatusBadRequest) + return + } + poiID, err := strconv.ParseInt(poiIDString, 10, 64) + if err != nil { + http.Error(w, "not able to parse poi ID", http.StatusBadRequest) + return + } + + g, err := s.store.GetGuidebyID(guideID) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if g == nil { + http.Error(w, "guide Not Found", http.StatusNotFound) + return + } + + poi, err := s.store.GetPoi(guideID, poiID) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if poi == nil { + http.Error(w, "poi Not Found", http.StatusNotFound) + return + } + coordinates, err := parseCoordinates(r.PostFormValue("latitude"), r.PostFormValue("longitude")) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + poi.Name = r.PostFormValue("name") + poi.Description = r.PostFormValue("description") + poi.Coordinate = coordinates + err = s.store.UpdatePoi(poi) + if err != nil { + poiForm := poiForm{ + PoiID: poiID, + GuideID: guideID, + GuideName: g.Name, + Name: poi.Name, + Description: poi.Description, + Latitude: r.PostFormValue("latitude"), + Longitude: r.PostFormValue("longitude"), + } + poiForm.Errors = append(poiForm.Errors, err.Error()) + w.WriteHeader(http.StatusBadRequest) + err := s.templateRegistry.renderPage(w, editPoiFormTemplate, poiForm) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + } + return + } + pois := s.store.GetAllPois(guideID) + err = s.templateRegistry.renderPartial(w, poiRowsTemplate, pois) + if err != nil { + http.Error(w, "internal server error", http.StatusInternalServerError) + } } } @@ -489,6 +641,8 @@ func (s *Server) Routes() http.Handler { router.HandleFunc("/guide/{id}/poi/create", s.HandleCreatePoiGet()).Methods(http.MethodGet) router.HandleFunc("/guide/{id}/poi/create", s.HandleCreatePoiPost()).Methods(http.MethodPost) router.HandleFunc("/guide/{guideID}/poi/{poiID}", s.HandlePoi()).Methods(http.MethodGet) + router.HandleFunc("/guide/{guideID}/poi/{poiID}/edit", s.HandleEditPoiGet()).Methods(http.MethodGet) + router.HandleFunc("/guide/{guideID}/poi/{poiID}", s.HandleEditPoiPatch()).Methods(http.MethodPatch) router.HandleFunc("/guide/{guideID}/poi/{poiID}", s.HandleDeletePoi()).Methods(http.MethodDelete) router.HandleFunc("/", HandleIndex()) return router @@ -499,10 +653,10 @@ func templateRoutes() *templateRegistry { partialTemplates := map[string]*template.Template{} //todo iterate over template dir - for _, templateName := range []string{indexTemplate, guideTemplate, createGuideFormTemplate, editGuideFormTemplate, createPoiFormTemplate} { + for _, templateName := range []string{indexTemplate, guideTemplate, createGuideFormTemplate, editGuideFormTemplate} { pageTemplates[templateName] = template.Must(template.ParseFS(fs, templatesDir+templateName, templatesDir+baseTemplate, templatesDir+guideRowsTemplate, templatesDir+poiRowsTemplate)) } - for _, templateName := range []string{guideRowsTemplate, poiViewTemplate} { + for _, templateName := range []string{guideRowsTemplate, poiRowsTemplate, poiViewTemplate, editPoiFormTemplate, createPoiFormTemplate} { partialTemplates[templateName] = template.Must(template.ParseFS(fs, templatesDir+templateName)) } @@ -551,5 +705,6 @@ const ( createGuideFormTemplate = "createGuideForm.html" editGuideFormTemplate = "editGuideForm.html" createPoiFormTemplate = "createPoiForm.html" + editPoiFormTemplate = "editPoiForm.html" poiViewTemplate = "poiView.html" ) diff --git a/server_test.go b/server_test.go index b886863..a55d0e8 100644 --- a/server_test.go +++ b/server_test.go @@ -73,83 +73,77 @@ func TestIndexHandler(t *testing.T) { } } -func TestGetIndexRoute(t *testing.T) { +func TestGetIndexReturns200OnEmptyStore(t *testing.T) { t.Parallel() - testCases := []struct { - path string - expectedStatusCode int - }{ - {"/", http.StatusOK}, //should be StatusFound 302 but httptest.Client follows redirects - {"/guides", http.StatusOK}, - {"/unknownroute", http.StatusNotFound}, - } - s := openTmpStorage(t) freePort, err := freeport.GetFreePort() if err != nil { t.Fatal(err) } address := fmt.Sprintf("localhost:%d", freePort) - server, err := guide.NewServer(address, s, os.Stdout) + server, err := guide.NewServer(address, s, os.Stdout) //empty store if err != nil { t.Fatal(err) } ts := httptest.NewServer(server.Routes()) defer ts.Close() - client := ts.Client() - for _, tc := range testCases { - res, err := client.Get(ts.URL + tc.path) - - if err != nil { - t.Fatal(err) - } + res, err := client.Get(ts.URL + "/") + if err != nil { + t.Fatal(err) + } - if res.StatusCode != tc.expectedStatusCode { - t.Errorf("for path %s want status %d OK, got %d", tc.path, tc.expectedStatusCode, res.StatusCode) - } + if res.StatusCode != http.StatusOK { + t.Errorf("for index path / want status 200 OK, got %d", res.StatusCode) } } -func TestGetGuideRoute(t *testing.T) { +func TestRoutes(t *testing.T) { t.Parallel() testCases := []struct { path string + httpMethod string expectedStatusCode int }{ - {"/guide/1", http.StatusOK}, //should be StatusFound 302 but httptest.Client follows redirects - {"/guide/2", http.StatusNotFound}, - } - - s := openTmpStorage(t) - - g, err := guide.NewGuide("San Cristobal", guide.WithValidStringCoordinates("10", "10")) - if err != nil { - t.Fatal(err) + {"/", http.MethodGet, http.StatusOK}, //should be StatusFound 302 but httptest.Client follows redirects + {"/guides", http.MethodGet, http.StatusOK}, + {"/unknownroute", http.MethodGet, http.StatusNotFound}, + {"/guide/1", http.MethodGet, http.StatusOK}, + {"/guide/42", http.MethodGet, http.StatusNotFound}, + {"/guide/", http.MethodGet, http.StatusNotFound}, + {"/guide/", http.MethodGet, http.StatusNotFound}, + {"/guide/blah", http.MethodGet, http.StatusBadRequest}, + {"/guides?q=non-existent", http.MethodGet, http.StatusNotFound}, //search + {"/guide/count", http.MethodGet, http.StatusOK}, //count + {"/guide/create", http.MethodGet, http.StatusOK}, + {"/guide/1/edit", http.MethodGet, http.StatusOK}, + {"/guide/42/edit", http.MethodGet, http.StatusNotFound}, + {"/guide/42/edit", http.MethodPost, http.StatusNotFound}, + {"/guide/1/poi/1", http.MethodGet, http.StatusOK}, + {"/guide/1/poi/", http.MethodGet, http.StatusNotFound}, + {"/guide/1/poi/42", http.MethodGet, http.StatusNotFound}, + {"/guide/42/poi/1", http.MethodGet, http.StatusNotFound}, + {"/guide/1/poi/create", http.MethodGet, http.StatusOK}, + {"/guide/42/poi/create", http.MethodGet, http.StatusNotFound}, + {"/guide/42/poi/create", http.MethodPost, http.StatusNotFound}, + {"/guide/1/poi/edit", http.MethodGet, http.StatusBadRequest}, + {"/guide/one/poi/edit", http.MethodGet, http.StatusBadRequest}, + {"/guide/1/poi/1/edit", http.MethodGet, http.StatusOK}, + {"/guide/42/poi/1/edit", http.MethodGet, http.StatusNotFound}, + {"/guide/1/poi/42/edit", http.MethodPatch, http.StatusMethodNotAllowed}, } - err = s.CreateGuide(&g) - if err != nil { - t.Fatal(err) - } - - freePort, err := freeport.GetFreePort() - if err != nil { - t.Fatal(err) - } - address := fmt.Sprintf("localhost:%d", freePort) - server, err := guide.NewServer(address, s, os.Stdout) - if err != nil { - t.Fatal(err) - } - + server := newProvisionedServer(t) ts := httptest.NewServer(server.Routes()) defer ts.Close() client := ts.Client() for _, tc := range testCases { - res, err := client.Get(ts.URL + tc.path) - + req, err := http.NewRequest(tc.httpMethod, ts.URL+tc.path, nil) + if err != nil { + t.Fatal(err) + } + res, err := client.Do(req) if err != nil { t.Fatal(err) } @@ -159,6 +153,7 @@ func TestGetGuideRoute(t *testing.T) { } } } + func TestGuideHandlerRendersMap(t *testing.T) { t.Parallel() s := openTmpStorage(t) @@ -206,7 +201,7 @@ func TestGuideHandlerRenders404NotFound(t *testing.T) { rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) - req = mux.SetURLVars(req, map[string]string{"id": strconv.FormatInt(1, 10)}) + req = mux.SetURLVars(req, map[string]string{"id": "1"}) handler := server.HandleGuide() handler(rec, req) @@ -286,37 +281,34 @@ func TestCreateGuideHandlerGetRendersForm(t *testing.T) { } } -//todo replace s.Guides with count -//func TestCreateGuideHandlerPostCreatesGuide(t *testing.T) { -// t.Parallel() -// s := openTmpStorage(t) -// server, err := guide.NewServer("localhost:8080", s, os.Stdout) -// if err != nil { -// t.Fatal(err) -// } -// rec := httptest.NewRecorder() -// form := strings.NewReader("name=Test&description=blah blah&latitude=10&longitude=10") -// req := httptest.NewRequest(http.MethodPost, "/guide/create", form) -// req.Header.Set("Content-Type", "application/x-www-form-urlencoded") -// handler := server.HandleCreateGuidePost() -// handler(rec, req) -// -// res := rec.Result() -// if res.StatusCode != http.StatusSeeOther { -// t.Errorf("expected status 303 SeeOther, got %d", res.StatusCode) -// } -// if len(s.Guides) != 1 { -// t.Error("want store to contain new guide") -// } -// -// g, err := s.GetGuidebyID(s.NextGuideKey - 1) -// if err != nil { -// t.Fatal(err) -// } -// if g.Description == "" { -// t.Error("want guide description to not be empty") -// } -//} +func TestCreateGuideHandlerPostCreatesGuide(t *testing.T) { + t.Parallel() + s := openTmpStorage(t) + server, err := guide.NewServer("localhost:8080", s, os.Stdout) + if err != nil { + t.Fatal(err) + } + rec := httptest.NewRecorder() + form := strings.NewReader("name=Test&description=blah blah&latitude=10&longitude=10") + req := httptest.NewRequest(http.MethodPost, "/guide/create", form) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + handler := server.HandleCreateGuidePost() + handler(rec, req) + + res := rec.Result() + if res.StatusCode != http.StatusSeeOther { + t.Errorf("expected status 303 SeeOther, got %d", res.StatusCode) + } + + g, err := s.GetGuidebyID(1) + if err != nil { + t.Fatal(err) + } + + if g.Description == "" { + t.Error("want guide description to not be empty") + } +} func TestCreateGuideHandlerPostFormErrors(t *testing.T) { t.Parallel() @@ -362,53 +354,6 @@ func TestCreateGuideHandlerPostFormErrors(t *testing.T) { } } -func TestEditGuideRoute(t *testing.T) { - t.Parallel() - testCases := []struct { - path string - expectedStatusCode int - }{ - {"/guide/1/edit", http.StatusOK}, //should be StatusFound 302 but httptest.Client follows redirects - {"/guide/2/edit", http.StatusNotFound}, - } - - s := openTmpStorage(t) - g, err := guide.NewGuide("San Cristobal", guide.WithValidStringCoordinates("10", "10")) - if err != nil { - t.Fatal(err) - } - err = s.CreateGuide(&g) - if err != nil { - t.Fatal(err) - } - - freePort, err := freeport.GetFreePort() - if err != nil { - t.Fatal(err) - } - address := fmt.Sprintf("localhost:%d", freePort) - server, err := guide.NewServer(address, s, os.Stdout) - if err != nil { - t.Fatal(err) - } - - ts := httptest.NewServer(server.Routes()) - defer ts.Close() - - client := ts.Client() - for _, tc := range testCases { - res, err := client.Get(ts.URL + tc.path) - - if err != nil { - t.Fatal(err) - } - - if res.StatusCode != tc.expectedStatusCode { - t.Errorf("for path %s want status %d OK, got %d", tc.path, tc.expectedStatusCode, res.StatusCode) - } - } -} - func TestDeleteGuideHandlerDeletesGuide(t *testing.T) { t.Parallel() s := openTmpStorage(t) @@ -472,22 +417,11 @@ func TestPoiHandlerRendersView(t *testing.T) { func TestCreatePoiHandlerGetRendersForm(t *testing.T) { t.Parallel() - s := openTmpStorage(t) - g, err := guide.NewGuide("San Cristobal", guide.WithValidStringCoordinates("10", "10")) - if err != nil { - t.Fatal(err) - } - err = s.CreateGuide(&g) - if err != nil { - t.Fatal(err) - } - server, err := guide.NewServer("localhost:8080", s, os.Stdout) - if err != nil { - t.Fatal(err) - } + server := newProvisionedServer(t) + rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodGet, "/guide/poi/create/1", nil) - req = mux.SetURLVars(req, map[string]string{"id": strconv.FormatInt(1, 10)}) + req := httptest.NewRequest(http.MethodGet, "/", nil) + req = mux.SetURLVars(req, map[string]string{"id": "1"}) handler := server.HandleCreatePoiGet() handler(rec, req) @@ -506,7 +440,6 @@ func TestCreatePoiHandlerGetRendersForm(t *testing.T) { } } -// TODO {"/guide/poi/create/1", http.StatusNotFound, "guide not found"}, test poi create on non-existing guide id func TestCreatePoiHandlerErrors(t *testing.T) { t.Parallel() testCases := []struct { @@ -514,8 +447,8 @@ func TestCreatePoiHandlerErrors(t *testing.T) { statusCode int want string }{ - {"/guide/poi/create", http.StatusBadRequest, "no guideid provided"}, - {"/guide/poi/create/one", http.StatusBadRequest, "no guideid provided"}, + {"/guide/1/poi/create", http.StatusBadRequest, "no guideid provided"}, + {"/guide/1/poi/create/one", http.StatusBadRequest, "no guideid provided"}, //{"/guide/poi/create/1", http.StatusNotFound, "guide not found"}, } s := openTmpStorage(t) @@ -596,7 +529,7 @@ func TestCreatePoiHandlerFormErrors(t *testing.T) { } } -func TestCreatePoiHandlerPost(t *testing.T) { +func TestCreatePoiHandlerPostCreatesPoi(t *testing.T) { t.Parallel() s := openTmpStorage(t) g, err := guide.NewGuide("San Cristobal", guide.WithValidStringCoordinates("16.7371", "-92.6375")) @@ -622,7 +555,7 @@ func TestCreatePoiHandlerPost(t *testing.T) { target := fmt.Sprintf("/guide/poi/create/%d", g.Id) req := httptest.NewRequest(http.MethodPost, target, form) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req = mux.SetURLVars(req, map[string]string{"id": strconv.FormatInt(1, 10)}) + req = mux.SetURLVars(req, map[string]string{"id": "1"}) handler := server.HandleCreatePoiPost() handler(rec, req) @@ -670,7 +603,7 @@ func TestDeletePoiHandlerDeletesPoi(t *testing.T) { req := httptest.NewRequest(http.MethodDelete, "/", nil) req = mux.SetURLVars(req, map[string]string{ - "id": strconv.FormatInt(poi.Id, 10), + "poiID": strconv.FormatInt(poi.Id, 10), "guideID": strconv.FormatInt(g.Id, 10), }) handler := server.HandleDeletePoi() @@ -690,8 +623,7 @@ func TestSearchGuide(t *testing.T) { t.Parallel() server := newProvisionedServer(t) rec := httptest.NewRecorder() - req := httptest.NewRequest(http.MethodPost, "/guide/search?q=guide", nil) - //req = mux.SetURLVars(req, map[string]string{"q": "guide"}) + req := httptest.NewRequest(http.MethodPost, "/", nil) handler := server.HandleGuides() handler(rec, req) @@ -785,6 +717,70 @@ func TestServer_HandleGuideCount(t *testing.T) { } } +func TestServer_HandleEditPoiGetRendersForm(t *testing.T) { + t.Parallel() + server := newProvisionedServer(t) + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req = mux.SetURLVars(req, map[string]string{ + "guideID": "1", + "poiID": "1", + }) + handler := server.HandleEditPoiGet() + handler(rec, req) + + res := rec.Result() + if res.StatusCode != http.StatusOK { + t.Errorf("expected status 200 OK, got %d", res.StatusCode) + } + body, err := io.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + want := "POI Values" + got := string(body) + if !strings.Contains(got, want) { + t.Errorf("want index to contain %s\nGot:\n%s", want, got) + } + +} + +func TestEditPoiHandlerPatchEditsPoi(t *testing.T) { + t.Parallel() + storage := openTmpStorage(t) + s := newProvisionedServerWithStore(storage, t) + + rec := httptest.NewRecorder() + form := strings.NewReader("name=Test&description=blah blah&latitude=10&longitude=10") + target := "/guide/1/poi/1" + req := httptest.NewRequest(http.MethodPost, target, form) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req = mux.SetURLVars(req, map[string]string{ + "guideID": "1", + "poiID": "1", + }) + handler := s.HandleEditPoiPatch() + handler(rec, req) + + res := rec.Result() + if res.StatusCode != http.StatusSeeOther { + t.Errorf("expected status 303 SeeOther, got %d", res.StatusCode) + } + + poi, err := storage.GetPoi(1, 1) + if err != nil { + t.Fatal(err) + } + if poi == nil { + t.Error("want guide to contain new poi") + } + + if poi.Description != "blah blah" { + t.Error("want poi description to be edited") + } + +} + // test helpers func openTmpStorage(t *testing.T) guide.Storage { tempDB := t.TempDir() + t.Name() + ".store" @@ -832,5 +828,39 @@ func newProvisionedServer(t *testing.T) *guide.Server { t.Fatal(err) } return &server +} +func newProvisionedServerWithStore(storage guide.Storage, t *testing.T) *guide.Server { + input := []string{"test 1", "guide 1", "test 2"} + for _, guideName := range input { + g, err := guide.NewGuide(guideName, guide.WithValidStringCoordinates("10", "10")) + if err != nil { + t.Fatal(err) + } + err = storage.CreateGuide(&g) + if err != nil { + t.Fatal(err) + } + for _, poiName := range input { + p, err := guide.NewPointOfInterest(poiName, g.Id, guide.PoiWithValidStringCoordinates("10", "10")) + if err != nil { + t.Fatal(err) + } + err = storage.CreatePoi(&p) + if err != nil { + t.Fatal(err) + } + } + } + + freePort, err := freeport.GetFreePort() + if err != nil { + t.Fatal(err) + } + address := fmt.Sprintf("localhost:%d", freePort) + server, err := guide.NewServer(address, storage, os.Stdout) + if err != nil { + t.Fatal(err) + } + return &server } diff --git a/store_test.go b/store_test.go index 872ce79..d62c6c6 100644 --- a/store_test.go +++ b/store_test.go @@ -137,7 +137,7 @@ func TestSQLiteStore_CountGuides(t *testing.T) { got := tempDB.CountGuides() if want != got { - t.Errorf("want CountGuides to return %d guides, got %d", want, got) + t.Errorf("want to return %d guides, got %d", want, got) } } diff --git a/templates/createGuideForm.html b/templates/createGuideForm.html index ca78769..1a90ad9 100644 --- a/templates/createGuideForm.html +++ b/templates/createGuideForm.html @@ -2,11 +2,9 @@ {{define "body"}}
-
- {{range .Errors}} -

{{ . }}

- {{end}} -
+

+ here some {{ .Errors }} +

Guide Values diff --git a/templates/createPoiForm.html b/templates/createPoiForm.html index d775b52..4821317 100644 --- a/templates/createPoiForm.html +++ b/templates/createPoiForm.html @@ -1,14 +1,13 @@ -{{define "title"}}Add Point of Interest to{{end}} -{{define "body"}} +{{define "createPoiForm.html"}}
- {{range .Errors}}

{{ . }}

{{end}}
- + +
POI Values @@ -45,7 +44,10 @@
- cancel + + cancel + +
diff --git a/templates/editPoiForm.html b/templates/editPoiForm.html new file mode 100644 index 0000000..5549a94 --- /dev/null +++ b/templates/editPoiForm.html @@ -0,0 +1,50 @@ +{{define "editPoiForm.html"}} +
+
+
+ {{range .Errors}} +

{{ . }}

+ {{end}} +
+
+
+ POI Values + +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+ cancel +
+
+
+{{end}} \ No newline at end of file diff --git a/templates/guide.html b/templates/guide.html index 5a5e15b..97a9780 100644 --- a/templates/guide.html +++ b/templates/guide.html @@ -31,27 +31,25 @@
{{template "mapscript" .}}
-
-
- - - - - - - - - {{template "poiRows.html" .}} - -
NameDescription
-
-
-
-
+ + + + + + + + + + + {{template "poiRows.html" .Pois}} + + + + + +

- Add Poi + Add Poi back

- - {{end}} \ No newline at end of file diff --git a/templates/poiRows.html b/templates/poiRows.html index 415dcf1..58ca635 100644 --- a/templates/poiRows.html +++ b/templates/poiRows.html @@ -1,13 +1,29 @@ {{define "poiRows.html"}} -{{range .Pois}} - - {{.Name}} - {{.Description}} - - Edit - Delete - - -{{end}} +
+
+ + + + + + + + + {{range .}} + + + + + + {{end}} + +
NameDescription
{{.Name}}{{.Description}} + Edit + Delete +
+
+
+
+
{{end}} \ No newline at end of file