bddgenx 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eae91a2de34a4ea84503ab6015501479f2a2483dafaa3b2a88d1930fc3271c0f
4
- data.tar.gz: d14373f08e54d394f993954acc6fff5c2ef275b942b2e0e381f3f65a02b0d986
3
+ metadata.gz: 94a0f0c3d58d4244048cc2b01054fbdde3ffaf2c627d36ea531d25bb64fd295d
4
+ data.tar.gz: fa9590c3a6e81c863ae6ea706e7bd24e0577cf9b31ef851c868f8c5a81c181bf
5
5
  SHA512:
6
- metadata.gz: 1630a5697a6f519bf92ecfd5f895b088fab0d4305f23efd5ea77204d1b292940c1381f9f282483116edb970890f7e6fd399dabc2a7468748fdb69fc552ba89db
7
- data.tar.gz: b2528be1f5c6a3ab8ea4555e168e2fbc7ca9677eb0ba76cc64c72c691d7478c69f12018a7ca8230dd57702dbf2c77e4e3bd00572cec3c6430a3f8b3d2e52fdf7
6
+ metadata.gz: a6cb0637977eab5b05790e0b0900d9862ebd4a7633b24d2a8d0c1daf0b5ff1259adcc1110f51e89607d07a335cb80f3cd05cca958c24332f93651d120a2eba4a
7
+ data.tar.gz: df1f2fe658088fb4fa27cea0be63547228d1ac677333191cbc68a59bd59c5b58ae056b75e3c4f7017b43b00879244fa63e81f3da905771f63692322a7397b133
@@ -11,141 +11,70 @@ module Bddgenx
11
11
  "PERMISSION" => "Teste de Permissão",
12
12
  "EDGE_CASE" => "Teste de Limite",
13
13
  "PERFORMANCE" => "Teste de Desempenho"
14
- }
15
-
16
- TIPOS_CENARIO = %w[
17
- SUCCESS FAILURE ERROR EXCEPTION
18
- VALIDATION PERMISSION EDGE_CASE PERFORMANCE
19
- ]
20
-
14
+ }.freeze
21
15
 
22
16
  def self.gerar_feature(historia)
23
- idioma = historia[:idioma] || 'pt'
24
-
25
- # Define os conectores de acordo com o idioma
17
+ idioma = historia[:idioma]
26
18
  palavras = {
27
- contexto: idioma == 'en' ? 'Background' : 'Contexto',
28
- cenario: idioma == 'en' ? 'Scenario' : 'Cenário',
29
- esquema: idioma == 'en' ? 'Scenario Outline' : 'Esquema do Cenário',
30
- exemplos: idioma == 'en' ? 'Examples' : 'Exemplos',
31
- regra: idioma == 'en' ? 'Rule' : 'Regra'
19
+ contexto: idioma == 'en' ? 'Background' : 'Contexto',
20
+ cenario: idioma == 'en' ? 'Scenario' : 'Cenário',
21
+ esquema: idioma == 'en' ? 'Scenario Outline' : 'Esquema do Cenário',
22
+ exemplos: idioma == 'en' ? 'Examples' : 'Exemplos',
23
+ regra: idioma == 'en' ? 'Rule' : 'Regra'
32
24
  }
33
25
 
34
- nome_base = historia[:quero].gsub(/[^a-zA-Z0-9]/, '_').downcase
35
- caminho = "features/#{nome_base}.feature"
26
+ nome_base = historia[:quero].gsub(/[^a-z0-9]/i, '_').downcase
27
+ caminho = "features/#{nome_base}.feature"
36
28
 
37
- conteudo = <<~HEADER
29
+ conteudo = <<~GHERKIN
38
30
  # language: #{idioma}
39
- Funcionalidade: #{historia[:quero].sub(/^Quero/, '').strip}
40
-
31
+ Funcionalidade: #{historia[:quero].sub(/^Quero\s*/, '')}
32
+
41
33
  #{historia[:como]}
42
34
  #{historia[:quero]}
43
35
  #{historia[:para]}
44
-
45
- HEADER
46
36
 
47
- # Regras
48
- if historia[:regras]&.any?
49
- conteudo += " #{palavras[:regra]}: #{historia[:regras].first}\n"
50
- historia[:regras][1..].each do |linha|
51
- conteudo += " #{linha}\n"
52
- end
53
- conteudo += "\n"
54
- end
37
+ GHERKIN
55
38
 
39
+ historia[:grupos].each_with_index do |grupo, idx|
40
+ tipo = grupo[:tipo]
41
+ tag = grupo[:tag]
42
+ passos = grupo[:passos]
43
+ exemplos = grupo[:exemplos]
56
44
 
57
- # Contexto
58
- if historia[:blocos]["CONTEXT"]&.any?
59
- conteudo += " #{palavras[:contexto]}:\n"
60
- historia[:blocos]["CONTEXT"].each { |p| conteudo += " #{p}\n" }
61
- conteudo += "\n"
62
- end
63
-
64
- # Cenários
65
- TIPOS_CENARIO.each do |tipo|
66
- passos = historia[:blocos][tipo]
67
- passos = passos&.reject { |l| l.strip.empty? } || []
68
45
  next if passos.empty?
69
46
 
70
- # Ignora geração duplicada se for um SUCCESS parametrizado com EXAMPLES
71
- if tipo == "SUCCESS" && historia[:blocos]["EXAMPLES"]&.any?
72
- possui_parametros = passos.any? { |p| p.include?('<') }
73
- next if possui_parametros
74
- end
75
-
76
- nome_teste = TIPOS_ISTQB[tipo] || palavras[:cenario]
77
- contexto = passos.first&.gsub(/^(Dado que|Given|Quando|When|Então|Then|E|And)/, '')&.strip || "Condição"
78
- resultado = passos.last&.gsub(/^(Então|Then|E|And)/, '')&.strip || "Resultado"
79
- nome_cenario = "#{nome_teste} - #{contexto} - #{resultado}"
80
-
81
- conteudo += " @#{tipo.downcase}\n"
82
- conteudo += " #{palavras[:cenario]}: #{nome_cenario}\n"
83
- passos.each { |p| conteudo += " #{p}\n" }
84
- conteudo += "\n"
85
- end
86
-
87
- # Esquema do Cenário com Exemplos
88
- if historia[:blocos]["EXAMPLES"]&.any?
89
- exemplo_bruto = historia[:blocos]["EXAMPLES"]
90
- grupos = dividir_examples(exemplo_bruto)
91
-
92
- grupos.each_with_index do |tabela, i|
93
- cabecalho = tabela.first.gsub('|', '').split.map(&:strip)
94
- linhas = tabela[1..].map { |linha| linha.split('|').reject(&:empty?).map(&:strip) }
95
-
96
- exemplos = { cabecalho: cabecalho, linhas: linhas }
47
+ linha_tag = ["@#{tipo.downcase}", ("@#{tag}" if tag)].compact.join(' ')
48
+ possui_parametros = passos.any? { |p| p.include?('<') } && exemplos.any?
97
49
 
98
- passos_outline = historia[:blocos]["SUCCESS"].select do |linha|
99
- cabecalho.any? { |coluna| linha.include?("<#{coluna}>") }
100
- end
101
-
102
- next if passos_outline.empty?
103
-
104
- conteudo += "\n"
105
- conteudo += idioma == 'en' ? " Scenario Outline: Example #{i + 1}\n" : " Esquema do Cenário: Exemplo #{i + 1}\n"
106
-
107
- passos_outline.each do |passo|
108
- conteudo += " #{passo}\n"
109
- end
110
-
111
- conteudo += "\n"
112
- conteudo += idioma == 'en' ? " Examples:\n" : " Exemplos:\n"
113
- conteudo += " #{tabela.first}\n"
114
- tabela[1..].each { |linha| conteudo += " #{linha}\n" }
50
+ if possui_parametros
51
+ conteudo << " #{linha_tag}\n"
52
+ conteudo << " #{palavras[:esquema]}: Exemplo #{idx + 1}\n"
53
+ passos.each { |p| conteudo << " #{p}\n" }
54
+ conteudo << "\n #{palavras[:exemplos]}:\n"
55
+ exemplos.each { |linha| conteudo << " #{linha}\n" }
56
+ conteudo << "\n"
57
+ else
58
+ nome_teste = TIPOS_ISTQB[tipo] || palavras[:cenario]
59
+ contexto = passos.first.gsub(/^(Dado|Quando|Então|E|Mas)\s+/, '').strip
60
+ resultado = passos.last .gsub(/^(Dado|Quando|Então|E|Mas)\s+/, '').strip
61
+ nome_ceno = "#{nome_teste} - #{contexto} - #{resultado}"
62
+
63
+ conteudo << " #{linha_tag}\n"
64
+ conteudo << " #{palavras[:cenario]}: #{nome_ceno}\n"
65
+ passos.each { |p| conteudo << " #{p}\n" }
66
+ conteudo << "\n"
115
67
  end
116
68
  end
117
69
 
118
70
  [caminho, conteudo]
119
71
  end
120
72
 
121
- # Salva o arquivo .feature gerado
122
- # Retorna true se o arquivo foi salvo com sucesso, false caso contrário
123
73
  def self.salvar_feature(caminho, conteudo)
124
- if conteudo.strip.empty?
125
- puts "⚠️ Nenhum conteúdo gerado para: #{caminho} (ignorado)"
126
- return false
127
- end
128
-
74
+ FileUtils.mkdir_p(File.dirname(caminho))
129
75
  File.write(caminho, conteudo)
130
76
  puts "✅ Arquivo .feature gerado: #{caminho}"
131
77
  true
132
78
  end
133
-
134
- def self.dividir_examples(tabela_bruta)
135
- grupos = []
136
- grupo_atual = []
137
-
138
- tabela_bruta.each do |linha|
139
- if linha.strip =~ /^\|\s*[\w\s]+\|/ && grupo_atual.any? && linha.strip == linha.strip.squeeze(" ")
140
- grupos << grupo_atual
141
- grupo_atual = [linha]
142
- else
143
- grupo_atual << linha
144
- end
145
- end
146
-
147
- grupos << grupo_atual unless grupo_atual.empty?
148
- grupos
149
- end
150
79
  end
151
80
  end
@@ -4,46 +4,69 @@ module Bddgenx
4
4
  VALIDATION PERMISSION EDGE_CASE PERFORMANCE
5
5
  EXAMPLES REGRA RULE
6
6
  ].freeze
7
+
7
8
  class Parser
8
9
  def self.ler_historia(caminho_arquivo)
9
- linhas = File.readlines(caminho_arquivo, encoding: 'utf-8').map(&:strip).reject(&:empty?)
10
+ linhas = File.readlines(caminho_arquivo, encoding: 'utf-8')
11
+ .map(&:strip)
12
+ .reject(&:empty?)
10
13
 
11
- # Detecta idioma
14
+ # idioma
12
15
  idioma = linhas.first.downcase.include?('# lang: en') ? 'en' : 'pt'
13
- linhas.shift if linhas.first.downcase.start_with?('#')
16
+ linhas.shift if linhas.first.downcase.start_with?('# lang:')
14
17
 
15
- # Ignora linhas que sejam blocos ou comentários até encontrar Como/Quero/Para
16
- cabecalho = []
18
+ # cabeçalho Gherkin: Como / Quero / Para
17
19
  until linhas.empty?
18
20
  linha = linhas.shift
19
- break if linha.start_with?("Como", "As") # início da história em pt ou en
21
+ break if linha =~ /^(Como |As a )/
20
22
  end
21
- como = linha
23
+ como = linha
22
24
  quero = linhas.shift
23
- para = linhas.shift
25
+ para = linhas.shift
24
26
 
25
27
  historia = {
26
- como: como,
27
- quero: quero,
28
- para: para,
29
- blocos: Hash.new { |h, k| h[k] = [] },
30
- regras: [],
31
- arquivo_origem: caminho_arquivo,
32
- idioma: idioma
28
+ como: como,
29
+ quero: quero,
30
+ para: para,
31
+ idioma: idioma,
32
+ grupos: [] # cada bloco ([TIPO]@tag) será um grupo
33
33
  }
34
34
 
35
- tipo_atual = nil
35
+ exemplos_mode = false
36
+ tipo_atual = nil
37
+ tag_atual = nil
36
38
 
37
39
  linhas.each do |linha|
38
- if linha.match?(/^\[(#{TIPOS_BLOCOS.join('|')})\]$/)
39
- tipo_atual = linha.gsub(/[\[\]]/, '')
40
+ # início de bloco EXAMPLES: modo exemplos para último grupo
41
+ if linha =~ /^\[EXAMPLES\](?:@(\w+))?$/
42
+ exemplos_mode = true
43
+ raise "Formato inválido: EXAMPLES sem bloco anterior" if historia[:grupos].empty?
44
+ historia[:grupos].last[:exemplos] = []
40
45
  next
41
46
  end
42
47
 
43
- if %w[REGRA RULE].include?(tipo_atual)
44
- historia[:regras] << linha
48
+ # início de um novo bloco (exceto EXAMPLES)
49
+ if linha =~ /^\[(#{TIPOS_BLOCOS.join('|')})\](?:@(\w+))?$/
50
+ exemplos_mode = false
51
+ tipo_atual = $1
52
+ tag_atual = $2
53
+ historia[:grupos] << {
54
+ tipo: tipo_atual,
55
+ tag: tag_atual,
56
+ passos: [],
57
+ exemplos: []
58
+ }
59
+ next
60
+ end
61
+
62
+ # atribuir linhas ao bloco atual
63
+ next if historia[:grupos].empty?
64
+ atual = historia[:grupos].last
65
+
66
+ if exemplos_mode
67
+ atual[:exemplos] << linha
45
68
  else
46
- historia[:blocos][tipo_atual] << linha if tipo_atual
69
+ atual[:passos] << linha
47
70
  end
48
71
  end
49
72
 
@@ -13,8 +13,9 @@ module Bddgenx
13
13
  if File.exist?(destino)
14
14
  puts "⚠️ PDF já existente: #{destino} — pulando geração."
15
15
  return
16
+ else
17
+ puts "📄 PDF gerado: #{destino}"
16
18
  end
17
- puts "📄 PDF gerado: #{destino}"
18
19
  end
19
20
  end
20
21
 
@@ -4,79 +4,68 @@ require_relative 'utils/verificador'
4
4
  module Bddgenx
5
5
  class StepsGenerator
6
6
  PADROES = {
7
- 'pt' => %w[Dado Quando Então E Mas Regra Contexto],
8
- 'en' => %w[Given When Then And But Rule Context]
7
+ 'pt' => %w[Dado Quando Então E Mas],
8
+ 'en' => %w[Given When Then And But]
9
9
  }
10
10
 
11
- TIPOS_BLOCOS = %w[
12
- CONTEXT SUCCESS FAILURE ERROR EXCEPTION
13
- VALIDATION PERMISSION EDGE_CASE PERFORMANCE
14
- EXAMPLES RULE
15
- ]
16
-
11
+ # Gera step definitions a partir da estrutura historia[:grupos]
17
12
  def self.gerar_passos(historia, nome_arquivo_feature)
18
13
  idioma = historia[:idioma] || 'pt'
19
14
  conectores = PADROES[idioma]
20
15
  passos_gerados = []
21
16
 
22
- grupos_examples = dividir_examples(historia[:blocos]["EXAMPLES"]) if historia[:blocos]["EXAMPLES"]&.any?
23
-
24
- TIPOS_BLOCOS.each do |tipo|
25
- blocos = tipo == "RULE" ? historia[:regras] : historia[:blocos][tipo]
26
- next unless blocos.is_a?(Array)
17
+ historia[:grupos].each do |grupo|
18
+ tipo = grupo[:tipo]
19
+ passos = grupo[:passos]
20
+ exemplos_brutos = grupo[:exemplos]
21
+ exemplos = exemplos_brutos&.any? ? dividir_examples(exemplos_brutos) : nil
27
22
 
28
- passos = blocos.dup
23
+ next unless passos.is_a?(Array) && passos.any?
29
24
 
30
25
  passos.each do |linha|
31
26
  conector = conectores.find { |c| linha.strip.start_with?(c) }
32
27
  next unless conector
33
28
 
34
- corpo = linha.strip.sub(/^#{conector}/, '').strip
35
-
36
- # Sanitiza aspas duplas envolvendo parâmetros, ex: "<nome>" -> <nome>
37
- corpo_sanitizado = corpo.gsub(/"(<[^>]+>)"/, '\1')
29
+ corpo = linha.strip.sub(/^#{conector}\s*/, '')
30
+ corpo_sanitizado = corpo.gsub(/"(<[^>]+>)"/, '\1')
38
31
 
39
- # Verifica se este passo pertence a algum grupo de exemplos
32
+ # tenta encontrar grupo de exemplos compatível, se existir
40
33
  grupo_exemplo_compat = nil
41
-
42
- if tipo == "SUCCESS" && grupos_examples
43
- grupos_examples.each do |grupo|
44
- cabecalho = grupo.first.gsub('|', '').split.map(&:strip)
34
+ if exemplos
35
+ exemplos.each do |tabela|
36
+ cabecalho = tabela.first.gsub('|', '').split.map(&:strip)
45
37
  if cabecalho.any? { |col| corpo.include?("<#{col}>") }
46
- grupo_exemplo_compat = {
47
- cabecalho: cabecalho,
48
- linhas: grupo[1..].map { |linha| linha.split('|').reject(&:empty?).map(&:strip) }
49
- }
38
+ linhas = tabela[1..].map { |l| l.gsub('|', '').split.map(&:strip) }
39
+ grupo_exemplo_compat = { cabecalho: cabecalho, linhas: linhas }
50
40
  break
51
41
  end
52
42
  end
53
43
  end
54
44
 
55
- if grupo_exemplo_compat
56
- # Substitui cada <param> por {tipo} dinamicamente
57
- corpo_parametrizado = corpo_sanitizado.gsub(/<([^>]+)>/) do
58
- nome = $1.strip
59
- tipo_param = detectar_tipo_param(nome, grupo_exemplo_compat)
60
- "{#{tipo_param}}"
45
+ # detecta todos os parametros <...> e gera arg list
46
+ nomes_param = corpo.scan(/<([^>]+)>/).flatten.map(&:strip)
47
+ if nomes_param.any?
48
+ corpo_param = corpo_sanitizado.dup
49
+ nomes_param.each do |nome|
50
+ tipo_param = grupo_exemplo_compat ? detectar_tipo_param(nome, grupo_exemplo_compat) : 'string'
51
+ corpo_param.gsub!(/<\s*#{Regexp.escape(nome)}\s*>/, "{#{tipo_param}}")
61
52
  end
62
-
63
- parametros = corpo.scan(/<([^>]+)>/).flatten.map { |p| p.strip.gsub(' ', '_') }
64
- param_list = parametros.join(', ')
53
+ args_list = nomes_param.map { |p| p.gsub(/\s+/, '_') }.join(', ')
54
+ pending_msg = corpo
65
55
  else
66
- corpo_parametrizado = corpo
67
- parametros = []
68
- param_list = ""
56
+ corpo_param = corpo
57
+ args_list = ''
58
+ pending_msg = corpo
69
59
  end
70
60
 
71
61
  passos_gerados << {
72
62
  conector: conector,
73
- raw: corpo,
74
- param: corpo_parametrizado,
75
- args: param_list,
76
- tipo: tipo
77
- } unless passos_gerados.any? { |p| p[:param] == corpo_parametrizado }
63
+ raw: pending_msg,
64
+ param: corpo_param,
65
+ args: args_list,
66
+ tipo: tipo
67
+ } unless passos_gerados.any? { |p| p[:param] == corpo_param }
78
68
  end
79
-
80
69
  end
81
70
 
82
71
  if passos_gerados.empty?
@@ -85,7 +74,7 @@ module Bddgenx
85
74
  end
86
75
 
87
76
  nome_base = File.basename(nome_arquivo_feature, '.feature')
88
- caminho = "steps/#{nome_base}_steps.rb"
77
+ caminho = "steps/#{nome_base}_steps.rb"
89
78
  FileUtils.mkdir_p(File.dirname(caminho))
90
79
 
91
80
  comentario = "# Step definitions para #{File.basename(nome_arquivo_feature)}"
@@ -94,14 +83,13 @@ module Bddgenx
94
83
 
95
84
  passos_gerados.each do |passo|
96
85
  conteudo += <<~STEP
97
- #{passo[:conector]}('#{passo[:param]}') do#{passo[:args].empty? ? '' : " |#{passo[:args]}|"}
98
- pending '#{idioma == 'en' ? 'Implement step' : 'Implementar passo'}: #{passo[:raw]}'
99
- end
100
-
86
+ #{passo[:conector]}('#{passo[:param]}') do#{passo[:args].empty? ? '' : " |#{passo[:args]}|"}
87
+ pending 'Implementar passo: #{passo[:raw]}'
88
+ end
89
+
101
90
  STEP
102
91
  end
103
92
 
104
- FileUtils.mkdir_p("steps")
105
93
  if Bddgenx::Verificador.gerar_arquivo_se_novo(caminho, conteudo)
106
94
  puts "✅ Step definitions gerados: #{caminho}"
107
95
  else
@@ -110,20 +98,12 @@ module Bddgenx
110
98
  true
111
99
  end
112
100
 
113
-
114
- def self.substituir_parametros(texto, exemplos)
115
- texto.gsub(/<([^>]+)>/) do |_match|
116
- nome = $1.strip
117
- tipo = detectar_tipo_param(nome, exemplos)
118
- "{#{tipo}}"
119
- end
120
- end
121
-
101
+ # Detecta tipo do parâmetro baseado em exemplos ou default
122
102
  def self.detectar_tipo_param(nome_coluna, exemplos)
123
- return 'string' unless exemplos && exemplos[:cabecalho].include?(nome_coluna)
103
+ return 'string' unless exemplos[:cabecalho].include?(nome_coluna)
124
104
 
125
105
  idx = exemplos[:cabecalho].index(nome_coluna)
126
- valores = exemplos[:linhas].map { |linha| linha[idx].to_s.strip }
106
+ valores = exemplos[:linhas].map { |l| l[idx].to_s.strip }
127
107
 
128
108
  return 'boolean' if valores.all? { |v| %w[true false].include?(v.downcase) }
129
109
  return 'int' if valores.all? { |v| v.match?(/^\d+$/) }
@@ -132,34 +112,20 @@ module Bddgenx
132
112
  'string'
133
113
  end
134
114
 
115
+ # Divide múltiplas tabelas de exemplo em grupos
135
116
  def self.dividir_examples(tabela_bruta)
136
117
  grupos = []
137
- grupo_atual = []
138
-
118
+ grupo = []
139
119
  tabela_bruta.each do |linha|
140
- if linha.strip =~ /^\|\s*[\w\s]+\|/ && grupo_atual.any? && linha.strip == linha.strip.squeeze(" ")
141
- grupos << grupo_atual
142
- grupo_atual = [linha]
120
+ if linha.strip =~ /^\|.*\|$/ && grupo.any? && linha.strip == linha.strip.squeeze(' ')
121
+ grupos << grupo
122
+ grupo = [linha]
143
123
  else
144
- grupo_atual << linha
124
+ grupo << linha
145
125
  end
146
126
  end
147
-
148
- grupos << grupo_atual unless grupo_atual.empty?
127
+ grupos << grupo unless grupo.empty?
149
128
  grupos
150
129
  end
151
-
152
-
153
- def self.extrair_exemplos(bloco)
154
- return nil unless bloco&.any?
155
-
156
- linhas = bloco.map(&:strip)
157
- cabecalho = linhas.first.gsub('|', '').split.map(&:strip)
158
- dados = linhas[1..].map { |linha| linha.gsub('|', '').split.map(&:strip) }
159
-
160
- { cabecalho: cabecalho, linhas: dados }
161
- end
162
130
  end
163
131
  end
164
-
165
-
@@ -3,32 +3,44 @@ require 'fileutils'
3
3
 
4
4
  module Bddgenx
5
5
  class Tracer
6
- ARQUIVO = 'output/rastreabilidade.csv'
6
+ def self.adicionar_entrada(historia, nome_arquivo_feature)
7
+ FileUtils.mkdir_p('output')
8
+ arquivo_csv = 'output/rastreabilidade.csv'
7
9
 
8
- def self.adicionar_entrada(historia, caminho_arquivo)
9
- FileUtils.mkdir_p("output")
10
+ cabecalho = ['Funcionalidade', 'Tipo', 'Tag', 'Cenário', 'Passo', 'Origem']
10
11
 
11
- CSV.open(ARQUIVO, File.exist?(ARQUIVO) ? 'a' : 'w', col_sep: ';') do |csv|
12
- unless File.exist?(ARQUIVO)
13
- csv << ["Funcionalidade", "Tipo de Teste", "Nome do Cenário", "Arquivo .feature"]
14
- end
12
+ linhas = []
15
13
 
16
- historia[:blocos].each do |tipo, passos|
17
- next if tipo == "CONTEXT" || tipo == "EXAMPLES" || passos.empty?
14
+ historia[:grupos].each_with_index do |grupo, idx|
15
+ tipo = grupo[:tipo]
16
+ tag = grupo[:tag]
17
+ passos = grupo[:passos]
18
18
 
19
- tipo_istqb = tipo.capitalize.gsub('_', ' ').capitalize
20
- contexto = passos.first&.gsub(/^(Dado que|Quando|Então|E)/, '')&.strip || "Condição"
21
- resultado = passos.last&.gsub(/^(Então|E)/, '')&.strip || "Resultado"
22
- nome_cenario = "#{tipo_istqb} - #{contexto} - #{resultado}"
19
+ nome_funcionalidade = historia[:quero].gsub(/^Quero\s*/, '').strip
20
+ nome_cenario = "Cenário #{idx + 1}"
23
21
 
24
- csv << [
25
- historia[:quero].sub(/^Quero/, '').strip,
26
- tipo_istqb,
22
+ passos.each do |passo|
23
+ linhas << [
24
+ nome_funcionalidade,
25
+ tipo,
26
+ tag || '-',
27
27
  nome_cenario,
28
- caminho_arquivo
28
+ passo,
29
+ File.basename(nome_arquivo_feature)
29
30
  ]
30
31
  end
31
32
  end
33
+
34
+ escrever_csv(arquivo_csv, cabecalho, linhas)
35
+ end
36
+
37
+ def self.escrever_csv(caminho, cabecalho, linhas)
38
+ novo_arquivo = !File.exist?(caminho)
39
+
40
+ CSV.open(caminho, 'a+', col_sep: ';', force_quotes: true) do |csv|
41
+ csv << cabecalho if novo_arquivo
42
+ linhas.each { |linha| csv << linha }
43
+ end
32
44
  end
33
45
  end
34
46
  end
@@ -1,34 +1,33 @@
1
1
  module Bddgenx
2
2
  class Validator
3
- TIPOS_CENARIO = %w[
4
- SUCCESS
5
- FAILURE
6
- ERROR
7
- EXCEPTION
8
- VALIDATION
9
- PERMISSION
10
- EDGE_CASE
11
- PERFORMANCE
12
- ]
13
-
14
3
  def self.validar(historia)
15
- valido = true
4
+ erros = []
5
+
6
+ unless historia[:como] && historia[:quero] && historia[:para]
7
+ erros << "❌ Cabeçalho incompleto (Como, Quero, Para obrigatórios)"
8
+ end
9
+
10
+ if historia[:grupos].empty?
11
+ erros << "❌ Nenhum grupo de blocos detectado"
12
+ else
13
+ historia[:grupos].each_with_index do |grupo, idx|
14
+ if grupo[:passos].empty? && grupo[:exemplos].empty?
15
+ erros << "❌ Grupo #{idx + 1} do tipo [#{grupo[:tipo]}] está vazio"
16
+ end
16
17
 
17
- if historia[:como].to_s.strip.empty? ||
18
- historia[:quero].to_s.strip.empty? ||
19
- historia[:para].to_s.strip.empty?
20
- puts "❌ História incompleta: 'Como', 'Quero' ou 'Para' está faltando."
21
- valido = false
18
+ if grupo[:tipo] == "EXAMPLES" && grupo[:exemplos].none? { |l| l.strip.start_with?('|') }
19
+ erros << "❌ Grupo de EXAMPLES no bloco #{idx + 1} não contém tabela válida"
20
+ end
21
+ end
22
22
  end
23
23
 
24
- cenarios_presentes = historia[:blocos].keys & TIPOS_CENARIO
25
- valido ||= historia[:blocos]["CONTEXT"]&.any? || historia[:regras]&.any?
26
- if cenarios_presentes.empty? && !valido
27
- puts "❌ Nenhum conteúdo válido detectado (cenários, contexto ou regras)."
24
+ if erros.any?
25
+ puts "⚠️ Erros encontrados no arquivo:"
26
+ erros.each { |e| puts " - #{e}" }
28
27
  return false
29
28
  end
30
29
 
31
- valido
30
+ true
32
31
  end
33
32
  end
34
33
  end
@@ -1,3 +1,3 @@
1
1
  module Bddgenx
2
- VERSION = "0.1.17"
2
+ VERSION = "0.1.18"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bddgenx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.17
4
+ version: 0.1.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Nascimento