package commit import ( "errors" "testing" "github.com/stretchr/testify/assert" ) func Test_parseHeader(t *testing.T) { type args struct { header []byte } tests := []struct { name string args args want *Commit errStr string errIs []error }{ { name: "missing type", args: args{header: []byte("add user sorting option")}, want: &Commit{Subject: "add user sorting option"}, errIs: []error{Err, ErrTypeMissing}, }, { name: "missing type with scope", args: args{ header: []byte("(user): add user sorting option"), }, want: &Commit{Scope: "user", Subject: "add user sorting option"}, errStr: `type is missing`, errIs: []error{Err, ErrType, ErrTypeMissing}, }, { name: "type only", args: args{header: []byte("feat: add user sorting option")}, want: &Commit{Type: "feat", Subject: "add user sorting option"}, }, { name: "type and scope", args: args{header: []byte("feat(user): add user sorting option")}, want: &Commit{ Type: "feat", Scope: "user", Subject: "add user sorting option", }, }, { name: "type and breaking", args: args{header: []byte("feat!: add user sorting option")}, want: &Commit{ Type: "feat", Subject: "add user sorting option", IsBreaking: true, }, }, { name: "type, scope and breaking", args: args{header: []byte("feat(user)!: add user sorting option")}, want: &Commit{ Type: "feat", Scope: "user", Subject: "add user sorting option", IsBreaking: true, }, }, { name: "type with underscore (_)", args: args{header: []byte("int_feat: add user sorting option")}, want: &Commit{ Type: "int_feat", Subject: "add user sorting option", }, }, { name: "type with hyphen (-)", args: args{header: []byte("int-feat: add user sorting option")}, want: &Commit{ Type: "int-feat", Subject: "add user sorting option", }, }, { name: "scope with underscopre (_)", args: args{ header: []byte("feat(user_sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user_sort", Subject: "add user sorting option", }, }, { name: "scope with hyphen (-)", args: args{ header: []byte("feat(user-sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user-sort", Subject: "add user sorting option", }, }, { name: "scope with slash (/)", args: args{ header: []byte("feat(user/sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user/sort", Subject: "add user sorting option", }, }, { name: "scope with period (.)", args: args{ header: []byte("feat(user.sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user.sort", Subject: "add user sorting option", }, }, { name: "scope with dollar sign ($)", args: args{ header: []byte("feat($user): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "$user", Subject: "add user sorting option", }, }, { name: "scope with star (*)", args: args{ header: []byte("feat(user*): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user*", Subject: "add user sorting option", }, }, { name: "scope with space ( )", args: args{ header: []byte("feat(user sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user sort", Subject: "add user sorting option", }, }, { name: "excess whitespace in type with scope", args: args{ header: []byte(" feat (user sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user sort", Subject: "add user sorting option", }, }, { name: "excess whitespace in scope", args: args{ header: []byte("feat( user sort ): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user sort", Subject: "add user sorting option", }, }, { name: "excess whitespace in subject", args: args{ header: []byte("feat(user): add user sorting option "), }, want: &Commit{ Type: "feat", Scope: "user", Subject: "add user sorting option", }, }, { name: "empty scope", args: args{ header: []byte("feat(): add user sorting option"), }, want: &Commit{ Type: "feat", Subject: "add user sorting option", }, }, { name: "multi-line header (LF)", args: args{ header: []byte("feat(user)!: add user sorting\nnoption"), }, want: &Commit{}, errStr: "invalid format: header has multiple lines", errIs: []error{ErrFormat, ErrMultiLineHeader}, }, { name: "multi-line header (CR)", args: args{ header: []byte("feat(user)!: add user sorting\roption"), }, want: &Commit{}, errStr: "invalid format: header has multiple lines", errIs: []error{Err, ErrFormat, ErrMultiLineHeader}, }, { name: "invalid type character", args: args{ header: []byte("feat/internal: add user sorting option"), }, want: &Commit{ Type: "feat/internal", Subject: "add user sorting option", }, errStr: `type must match: ^[\w-]+$`, errIs: []error{Err, ErrType, ErrTypeFormat}, }, { name: "invalid type character with scope", args: args{ header: []byte("feat/internal(user): add user sorting option"), }, want: &Commit{ Type: "feat/internal", Scope: "user", Subject: "add user sorting option", }, errStr: `type must match: ^[\w-]+$`, errIs: []error{Err, ErrType, ErrTypeFormat}, }, { name: "invalid scope character", args: args{ header: []byte("feat(user#sort): add user sorting option"), }, want: &Commit{ Type: "feat", Scope: "user#sort", Subject: "add user sorting option", }, errStr: `scope must match: ^[\w\$\.\/\-\* ]+$`, errIs: []error{Err, ErrScope, ErrScopeFormat}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := parseHeader(tt.args.header) if tt.errStr != "" { assert.EqualError(t, err, tt.errStr) } if len(tt.errIs) > 0 { for _, errIs := range tt.errIs { assert.True(t, errors.Is(err, errIs)) } } if len(tt.errIs) == 0 && tt.errStr == "" { assert.NoError(t, err) } assert.Equal(t, tt.want, got) }) } } func Test_footers(t *testing.T) { type args struct { paragraph []byte } tests := []struct { name string args args want []*rawFooter }{ { name: "without footer", args: args{[]byte("this is not a fooder")}, want: []*rawFooter{}, }, { name: "token footer on second line", args: args{[]byte("this is not a fooder\nDone-By: John")}, want: []*rawFooter{}, }, { name: "ticket footer on second line", args: args{[]byte("this is not a fooder\nFixes #42")}, want: []*rawFooter{}, }, { name: "breaking change footer on second line", args: args{[]byte("this is not a fooder\nBREAKING CHANGE: Oops")}, want: []*rawFooter{}, }, { name: "token footer", args: args{[]byte("Reviewed-By: John Smith")}, want: []*rawFooter{ {Name: []byte("Reviewed-By"), Body: []byte("John Smith")}, }, }, { name: "breaking change footer", args: args{[]byte("BREAKING CHANGE: Oopsy")}, want: []*rawFooter{ {Name: []byte("BREAKING CHANGE"), Body: []byte("Oopsy")}, }, }, { name: "ticket footer", args: args{[]byte("Fixes #82")}, want: []*rawFooter{ {Name: []byte("Fixes"), Body: []byte("#82"), Ref: true}, }, }, { name: "multiple token footers", args: args{[]byte( "Reviewed-By: John\n" + "Committer: Smith\n", )}, want: []*rawFooter{ {Name: []byte("Reviewed-By"), Body: []byte("John")}, {Name: []byte("Committer"), Body: []byte("Smith")}, }, }, { name: "multiple ticket footers", args: args{[]byte("Fixes #82\nFixes #74")}, want: []*rawFooter{ {Name: []byte("Fixes"), Body: []byte("#82"), Ref: true}, {Name: []byte("Fixes"), Body: []byte("#74"), Ref: true}, }, }, { name: "multiple breaking change footers", args: args{[]byte( "BREAKING CHANGE: Oopsy\n" + "BREAKING CHANGE: Again!", )}, want: []*rawFooter{ {Name: []byte("BREAKING CHANGE"), Body: []byte("Oopsy")}, {Name: []byte("BREAKING CHANGE"), Body: []byte("Again!")}, }, }, { name: "mixture of footer types", args: args{[]byte( "Fixes #930\n" + "BREAKING CHANGE: Careful!\n" + "Reviewed-By: Maria\n", )}, want: []*rawFooter{ {Name: []byte("Fixes"), Body: []byte("#930"), Ref: true}, {Name: []byte("BREAKING CHANGE"), Body: []byte("Careful!")}, {Name: []byte("Reviewed-By"), Body: []byte("Maria")}, }, }, { name: "multi-line footers", args: args{[]byte( "Description: Lorem ipsum dolor sit amet, consectetur\n" + "adipiscing elit.Praesent eleifend lorem non purus\n" + "finibus, interdum hendrerit sem bibendum.\n" + "Fixes #94\n" + "Misc-Other: Etiam porttitor mollis nulla, egestas\n" + "facilisis nisi molestie ut. Quisque mi mi, commodo\n" + "ut mattis a, scelerisque eu elit.\n" + "BREAKING CHANGE: Duis id nulla eget velit maximus\n" + "varius et egestas sem. Ut mi risus, pretium quis\n" + "cursus quis, porttitor in ipsum.\n", )}, want: []*rawFooter{ { Name: []byte("Description"), Body: []byte("Lorem ipsum dolor sit amet, consectetur\n" + "adipiscing elit.Praesent eleifend lorem non purus\n" + "finibus, interdum hendrerit sem bibendum."), }, {Name: []byte("Fixes"), Body: []byte("#94"), Ref: true}, { Name: []byte("Misc-Other"), Body: []byte("Etiam porttitor mollis nulla, egestas\n" + "facilisis nisi molestie ut. Quisque mi mi, commodo\n" + "ut mattis a, scelerisque eu elit."), }, { Name: []byte("BREAKING CHANGE"), Body: []byte("Duis id nulla eget velit maximus\n" + "varius et egestas sem. Ut mi risus, pretium quis\n" + "cursus quis, porttitor in ipsum."), }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := footers(tt.args.paragraph) assert.Equal(t, tt.want, got) }) } } func Test_paragraph(t *testing.T) { type args struct { input []byte } tests := []struct { name string args args want [][]byte }{ { name: "single line string", args: args{input: []byte("hello world\n")}, want: [][]byte{[]byte("hello world")}, }, { name: "multi-line line string", args: args{input: []byte("hello world\nthe brown fox\n")}, want: [][]byte{[]byte("hello world\nthe brown fox")}, }, { name: "excess whitespace", args: args{input: []byte(" \n hello world\nthe brown fox \n ")}, want: [][]byte{[]byte("hello world\nthe brown fox")}, }, { name: "multiple paragraphs", args: args{input: []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\n" + "elit.Praesent eleifend lorem non purus finibus,\n" + "interdum hendrerit sem bibendum.\n" + "\n" + "Etiam porttitor mollis nulla, egestas facilisis nisi\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\n" + "scelerisque eu elit.\n" + "\n" + "Duis id nulla eget velit maximus varius et egestas\n" + "sem. Ut mi risus, pretium quis cursus quis,\n" + "porttitor in ipsum.\n", )}, want: [][]byte{ []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\n" + "elit.Praesent eleifend lorem non purus finibus,\n" + "interdum hendrerit sem bibendum.", ), []byte( "Etiam porttitor mollis nulla, egestas facilisis nisi\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\n" + "scelerisque eu elit.", ), []byte( "Duis id nulla eget velit maximus varius et egestas\n" + "sem. Ut mi risus, pretium quis cursus quis,\n" + "porttitor in ipsum.", ), }, }, { name: "paragraphs with surrounding whitespace", args: args{input: []byte( "\n" + " \n" + " Lorem ipsum dolor sit amet, consectetur adipiscing\n" + "elit.Praesent eleifend lorem non purus finibus,\n" + "interdum hendrerit sem bibendum. \n" + "\n" + "\n" + " Etiam porttitor mollis nulla, egestas facilisis nisi\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\n" + "scelerisque eu elit.\n" + " \n" + " ", )}, want: [][]byte{ []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\n" + "elit.Praesent eleifend lorem non purus finibus,\n" + "interdum hendrerit sem bibendum.", ), []byte( "Etiam porttitor mollis nulla, egestas facilisis nisi\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\n" + "scelerisque eu elit.", ), }, }, { name: "CRLF line separator", args: args{input: []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\r\n" + "elit.Praesent eleifend lorem non purus finibus,\r\n" + "interdum hendrerit sem bibendum.\r\n" + "\r\n" + "Etiam porttitor mollis nulla, egestas facilisis nisi\r\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\r\n" + "scelerisque eu elit.\r\n", )}, want: [][]byte{ []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\n" + "elit.Praesent eleifend lorem non purus finibus,\n" + "interdum hendrerit sem bibendum.", ), []byte( "Etiam porttitor mollis nulla, egestas facilisis nisi\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\n" + "scelerisque eu elit.", ), }, }, { name: "CR line separator", args: args{input: []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\r" + "elit.Praesent eleifend lorem non purus finibus,\r" + "interdum hendrerit sem bibendum.\r" + "\r" + "Etiam porttitor mollis nulla, egestas facilisis nisi\r" + "molestie ut. Quisque mi mi, commodo ut mattis a,\r" + "scelerisque eu elit.\r", )}, want: [][]byte{ []byte( "Lorem ipsum dolor sit amet, consectetur adipiscing\n" + "elit.Praesent eleifend lorem non purus finibus,\n" + "interdum hendrerit sem bibendum.", ), []byte( "Etiam porttitor mollis nulla, egestas facilisis nisi\n" + "molestie ut. Quisque mi mi, commodo ut mattis a,\n" + "scelerisque eu elit.", ), }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := paragraphs(tt.args.input) assert.Equal(t, tt.want, got) }) } }