[add] page config

This commit is contained in:
Max 2023-10-09 12:10:06 +08:00
parent 92a3405f75
commit 71183fa17c
9 changed files with 441 additions and 28 deletions

View file

@ -542,8 +542,9 @@ func TestEditorRender(t *testing.T) {
t.Fatal(err)
}
assert.IsType(t, &core.ResponseEditor{}, res)
assert.NotEmpty(t, res.(*core.ResponseEditor).HTML)
assert.IsType(t, &core.ResponseEditorRender{}, res)
assert.NotEmpty(t, res.(*core.ResponseEditorRender).HTML)
assert.NotEmpty(t, res.(*core.ResponseEditorRender).Config)
}
func TestEditorRenderWithQuery(t *testing.T) {
@ -563,8 +564,8 @@ func TestEditorRenderWithQuery(t *testing.T) {
t.Fatal(err)
}
assert.IsType(t, &core.ResponseEditor{}, res)
assert.NotEmpty(t, res.(*core.ResponseEditor).HTML)
assert.IsType(t, &core.ResponseEditorRender{}, res)
assert.NotEmpty(t, res.(*core.ResponseEditorRender).HTML)
}
func TestEditorPageSource(t *testing.T) {

View file

@ -7,15 +7,16 @@ import (
)
// EditorRender render HTML for the editor
func (page *Page) EditorRender(request *Request) (*ResponseEditor, error) {
func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error) {
html := page.Codes.HTML.Code
res := &ResponseEditor{
res := &ResponseEditorRender{
HTML: "",
CSS: page.Codes.CSS.Code,
Scripts: []string{},
Styles: []string{},
Warnings: []string{},
Config: page.GetConfig(),
Setting: map[string]interface{}{},
}

View file

@ -38,11 +38,12 @@ type IPage interface {
Load() error
Get() *Page
GetConfig() *PageConfig
Save(request *RequestSource) error
SaveTemp(request *RequestSource) error
Remove() error
EditorRender(request *Request) (*ResponseEditor, error)
EditorRender(request *Request) (*ResponseEditorRender, error)
EditorPageSource() SourceData
EditorScriptSource() SourceData
EditorStyleSource() SourceData

61
sui/core/json.go Normal file
View file

@ -0,0 +1,61 @@
package core
import (
jsoniter "github.com/json-iterator/go"
)
// UnmarshalJSON Custom JSON unmarshal function for PageMock
func (mock *PageMock) UnmarshalJSON(data []byte) error {
if mock == nil {
return nil
}
type Alias struct {
Method string `json:"method"`
Params map[string]string `json:"params,omitempty"`
Query map[string]interface{} `json:"query,omitempty"`
Headers map[string]interface{} `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
}
aux := &Alias{}
if err := jsoniter.Unmarshal(data, &aux); err != nil {
return err
}
method := aux.Method
if method == "" {
method = "GET"
}
mock.Body = aux.Body
mock.Method = method
mock.Params = aux.Params
mock.Query = convertRecordToMap(aux.Query)
mock.Headers = convertRecordToMap(aux.Headers)
return nil
}
// Helper function to convert TypeScript Record<string, string | string[]> to map[string][]string
func convertRecordToMap(record map[string]interface{}) map[string][]string {
if record == nil {
return nil
}
result := make(map[string][]string)
for key, value := range record {
switch v := value.(type) {
case string:
result[key] = []string{v}
case []interface{}:
strValues := make([]string, len(v))
for i, item := range v {
if str, ok := item.(string); ok {
strValues[i] = str
}
}
result[key] = strValues
}
}
return result
}

228
sui/core/json_test.go Normal file
View file

@ -0,0 +1,228 @@
package core
import (
"reflect"
"testing"
jsoniter "github.com/json-iterator/go"
)
func TestRequestSourceUnmarshalJSON(t *testing.T) {
testCases := []struct {
name string
jsonData string
expected *RequestSource
shouldFail bool
}{
{
name: "Valid JSON with string query and headers",
jsonData: `{
"uid": "123",
"mock": {
"method": "GET",
"query": {
"q1": "value1",
"q2": "value2"
},
"headers": {
"header1": "value1",
"header2": "value2"
}
}
}`,
expected: &RequestSource{
UID: "123",
Mock: &PageMock{
Method: "GET",
Query: map[string][]string{
"q1": {"value1"},
"q2": {"value2"},
},
Headers: map[string][]string{
"header1": {"value1"},
"header2": {"value2"},
},
},
},
shouldFail: false,
},
{
name: "Valid JSON with array query and headers",
jsonData: `{
"uid": "456",
"mock": {
"method": "POST",
"query": {
"q1": ["value1", "value2"],
"q2": ["value3"]
},
"headers": {
"header1": ["value1", "value2"],
"header2": "value3"
}
}
}`,
expected: &RequestSource{
UID: "456",
Mock: &PageMock{
Method: "POST",
Query: map[string][]string{
"q1": {"value1", "value2"},
"q2": {"value3"},
},
Headers: map[string][]string{
"header1": {"value1", "value2"},
"header2": {"value3"},
},
},
},
shouldFail: false,
},
{
name: "Valid JSON with invalid query",
jsonData: `{
"uid": "789",
"mock": {
"method": "PUT",
"query":"1203"
}
}`,
expected: nil,
shouldFail: true,
},
// Add more test cases here to cover other scenarios.
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
var requestSource RequestSource
err := jsoniter.Unmarshal([]byte(testCase.jsonData), &requestSource)
if testCase.shouldFail {
if err == nil {
t.Errorf("%s: Expected unmarshal to fail, but it succeeded", testCase.name)
}
} else {
if err != nil {
t.Errorf("Unmarshal failed: %v", err)
}
if !reflect.DeepEqual(requestSource, *testCase.expected) {
t.Errorf("Unmarshaled result does not match expected result")
}
}
})
}
}
func TestPageConfigUnmarshalJSON(t *testing.T) {
testCases := []struct {
name string
jsonData string
expected *PageConfig
shouldFail bool
}{
{
name: "Valid JSON with PageSetting and PageMock",
jsonData: `{
"title": "Page Title",
"mock": {
"method": "GET",
"query": {
"q1": "value1",
"q2": "value2"
},
"headers": {
"header1": "value1",
"header2": "value2"
}
}
}`,
expected: &PageConfig{
PageSetting: PageSetting{
Title: "Page Title",
},
Mock: &PageMock{
Method: "GET",
Query: map[string][]string{
"q1": {"value1"},
"q2": {"value2"},
},
Headers: map[string][]string{
"header1": {"value1"},
"header2": {"value2"},
},
},
},
shouldFail: false,
},
{
name: "Valid JSON with PageSetting only",
jsonData: `{
"title": "Page Title"
}`,
expected: &PageConfig{
PageSetting: PageSetting{
Title: "Page Title",
},
Mock: nil,
},
shouldFail: false,
},
{
name: "Valid JSON with PageMock only",
jsonData: `{
"mock": {
"method": "GET",
"query": {
"q1": "value1",
"q2": "value2"
}
}
}`,
expected: &PageConfig{
Mock: &PageMock{
Method: "GET",
Query: map[string][]string{
"q1": {"value1"},
"q2": {"value2"},
},
},
},
shouldFail: false,
},
{
name: "Valid JSON with invalid query",
jsonData: `{
"title": "Page Title",
"mock": {
"method": "PUT",
"query": "invalid query"
}
}`,
expected: nil,
shouldFail: true,
},
// Add more test cases here to cover other scenarios.
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
var pageConfig PageConfig
err := jsoniter.Unmarshal([]byte(testCase.jsonData), &pageConfig)
if testCase.shouldFail {
if err == nil {
t.Errorf("%s: Expected unmarshal to fail, but it succeeded", testCase.name)
}
} else {
if err != nil {
t.Errorf("Unmarshal failed: %v", err)
}
if !reflect.DeepEqual(pageConfig, *testCase.expected) {
t.Errorf("Unmarshaled result does not match expected result")
}
}
})
}
}

View file

@ -1,24 +1,20 @@
package core
import jsoniter "github.com/json-iterator/go"
// Get get the base info
func (page *Page) Get() *Page {
return page
}
// GetHTML get the html
func (page *Page) GetHTML() {}
// GetScript get the script
func (page *Page) GetScript() {}
// GetStyle get the style
func (page *Page) GetStyle() {}
// GetData get the data
func (page *Page) GetData() {}
// SaveTemp save the temp
func (page *Page) SaveTemp() {}
// Save the page
func (page *Page) Save() {}
// GetConfig get the config
func (page *Page) GetConfig() *PageConfig {
if page.Config == nil && page.Codes.CONF.Code != "" {
var config PageConfig
err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config)
if err == nil {
page.Config = &config
}
}
return page.Config
}

View file

@ -12,6 +12,7 @@ type DSL struct {
type Page struct {
Route string `json:"route"`
Name string `json:"name,omitempty"`
Config *PageConfig `json:"-"`
Path string `json:"-"`
Codes SourceCodes `json:"-"`
Document []byte `json:"-"`
@ -79,8 +80,8 @@ type Request struct {
Payload map[string]interface{} `json:"payload,omitempty"`
Query map[string][]string `json:"query,omitempty"`
Params map[string]string `json:"params,omitempty"`
Headers []string `json:"headers,omitempty"`
Body []byte `json:"body,omitempty"`
Headers map[string][]string `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
Theme string `json:"theme,omitempty"`
Locale string `json:"locale,omitempty"`
}
@ -94,23 +95,28 @@ type RequestSource struct {
Script *SourceData `json:"script,omitempty"`
Data *SourceData `json:"data,omitempty"`
Board *BoardSourceData `json:"board,omitempty"`
Mock *PageMock `json:"mock,omitempty"`
Setting *PageSetting `json:"setting,omitempty"`
NeedToSave struct {
Page bool `json:"page,omitempty"`
Style bool `json:"style,omitempty"`
Script bool `json:"script,omitempty"`
Data bool `json:"data,omitempty"`
Board bool `json:"board,omitempty"`
Mock bool `json:"mock,omitempty"`
Setting bool `json:"setting,omitempty"`
Validate bool `json:"validate,omitempty"`
} `json:"needToSave,omitempty"`
}
// ResponseEditor is the struct for the response
type ResponseEditor struct {
// ResponseEditorRender is the struct for the response
type ResponseEditorRender struct {
HTML string `json:"html,omitempty"`
CSS string `json:"css,omitempty"`
Scripts []string `json:"scripts,omitempty"`
Styles []string `json:"styles,omitempty"`
Setting map[string]interface{} `json:"setting,omitempty"`
Config *PageConfig `json:"config,omitempty"`
Warnings []string `json:"warnings,omitempty"`
}
@ -126,6 +132,37 @@ type BoardSourceData struct {
Style string `json:"style,omitempty"`
}
// PageMock is the struct for the request
type PageMock struct {
Method string `json:"method,omitempty"`
Params map[string]string `json:"params,omitempty"`
Query map[string][]string `json:"query,omitempty"`
Headers map[string][]string `json:"headers,omitempty"`
Body interface{} `json:"body,omitempty"`
}
// PageConfig is the struct for the page config
type PageConfig struct {
PageSetting `json:",omitempty"`
Mock *PageMock `json:"mock,omitempty"`
}
// PageSetting is the struct for the page setting
type PageSetting struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
SEO *PageSEO `json:"seo,omitempty"`
}
// PageSEO is the struct for the page seo
type PageSEO struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Keywords string `json:"keywords,omitempty"`
Image string `json:"image,omitempty"`
URL string `json:"url,omitempty"`
}
// SourceCodes is the struct for the page codes
type SourceCodes struct {
HTML Source `json:"-"`
@ -134,6 +171,7 @@ type SourceCodes struct {
TS Source `json:"-"`
LESS Source `json:"-"`
DATA Source `json:"-"`
CONF Source `json:"-"`
}
// Source is the struct for the source

View file

@ -5,6 +5,7 @@ import (
"path/filepath"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/kun/log"
"github.com/yaoapp/yao/sui/core"
)
@ -221,6 +222,7 @@ func (tmpl *Template) CreatePage(route string) (core.IPage, error) {
JS: core.Source{File: fmt.Sprintf("%s.js", name)},
TS: core.Source{File: fmt.Sprintf("%s.ts", name)},
LESS: core.Source{File: fmt.Sprintf("%s.less", name)},
CONF: core.Source{File: fmt.Sprintf("%s.config", name)},
},
},
}, nil
@ -272,6 +274,7 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) {
DATA: core.Source{File: fmt.Sprintf("%s.json", name)},
TS: core.Source{File: fmt.Sprintf("%s.ts", name)},
LESS: core.Source{File: fmt.Sprintf("%s.less", name)},
CONF: core.Source{File: fmt.Sprintf("%s.config", name)},
},
},
}, nil
@ -345,6 +348,16 @@ func (page *Page) Load() error {
page.Codes.DATA.Code = string(dataCode)
}
// Read the config code
confFile := filepath.Join(page.Path, page.Codes.CONF.File)
if exist, _ := page.tmpl.local.fs.Exists(confFile); exist {
confCode, err := page.tmpl.local.fs.ReadFile(confFile)
if err != nil {
return err
}
page.Codes.CONF.Code = string(confCode)
}
// Set the page document
page.Document = page.tmpl.Document
return nil
@ -422,6 +435,13 @@ func (page *Page) save(path string, request *core.RequestSource) error {
}
}
if request.NeedToSave.Setting || request.NeedToSave.Mock {
err := page.saveSetting(path, request.Setting, request.Mock)
if err != nil {
return err
}
}
return nil
}
@ -486,6 +506,34 @@ func (page *Page) saveData(path string, src *core.SourceData) error {
return err
}
func (page *Page) saveSetting(path string, setting *core.PageSetting, mock *core.PageMock) error {
config := map[string]interface{}{}
if setting != nil {
err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config)
if err != nil {
return err
}
}
if mock != nil {
config["mock"] = mock
}
configBytes, err := jsoniter.MarshalIndent(config, "", " ")
if err != nil {
return err
}
if len(config) > 0 {
dataFile := filepath.Join(path, page.Codes.CONF.File)
_, err = page.tmpl.local.fs.WriteFile(dataFile, configBytes, 0644)
return err
}
return nil
}
// AssetScript get the script
func (page *Page) AssetScript() (*core.Asset, error) {

View file

@ -42,6 +42,7 @@ func TestTemplatePages(t *testing.T) {
assert.Equal(t, name+".less", page.Codes.LESS.File)
assert.Equal(t, name+".ts", page.Codes.TS.File)
assert.Equal(t, name+".json", page.Codes.DATA.File)
assert.Equal(t, name+".config", page.Codes.CONF.File)
}
}
@ -444,6 +445,44 @@ func TestPageSaveTempData(t *testing.T) {
assert.Nil(t, err)
}
func TestPageSaveTempSetting(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
const payload = `{
"page": null,
"style": null,
"script": null,
"setting": { "title": "Home Page | {{ $global.title }}" },
"mock": { "params": { "id": "1" } },
"needToSave": {
"page": false,
"style": false,
"script": false,
"mock": true,
"setting": true,
"board": false,
"validate": true
}
}`
req := &core.RequestSource{UID: "19e09e7e-9e19-44c1-bbab-2a55c51c9df3"}
jsoniter.Unmarshal([]byte(payload), &req)
page, err := tmpl.Page("/index")
if err != nil {
t.Fatalf("Page error: %v", err)
}
err = page.SaveTemp(req)
assert.Nil(t, err)
}
func TestPageSave(t *testing.T) {
tests := prepare(t)
defer clean()